mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-28 13:47:09 +08:00
Added initial set of hook-related nodes, added code to register hooks for loras/model-as-loras, small renaming/refactoring
This commit is contained in:
parent
f5abdc6f86
commit
9ded65a616
176
comfy/hooks.py
176
comfy/hooks.py
@ -1,8 +1,14 @@
|
|||||||
from typing import TYPE_CHECKING, List, Dict, Tuple
|
from typing import TYPE_CHECKING, List, Dict, Tuple
|
||||||
import enum
|
import enum
|
||||||
|
import torch
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from comfy.model_patcher import ModelPatcher
|
||||||
from comfy.model_base import BaseModel
|
from comfy.model_base import BaseModel
|
||||||
|
from comfy.sd import CLIP
|
||||||
|
import comfy.lora
|
||||||
|
import comfy.model_management
|
||||||
|
from node_helpers import conditioning_set_values
|
||||||
|
|
||||||
class EnumHookMode(enum.Enum):
|
class EnumHookMode(enum.Enum):
|
||||||
MinVram = "minvram"
|
MinVram = "minvram"
|
||||||
@ -11,7 +17,7 @@ class EnumHookMode(enum.Enum):
|
|||||||
class HookRef:
|
class HookRef:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class HookWeight:
|
class Hook:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.hook_ref = HookRef()
|
self.hook_ref = HookRef()
|
||||||
self.hook_keyframe = HookWeightKeyframeGroup()
|
self.hook_keyframe = HookWeightKeyframeGroup()
|
||||||
@ -28,36 +34,36 @@ class HookWeight:
|
|||||||
self.hook_keyframe.reset()
|
self.hook_keyframe.reset()
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
c = HookWeight()
|
c = Hook()
|
||||||
c.hook_ref = self.hook_ref
|
c.hook_ref = self.hook_ref
|
||||||
c.hook_keyframe = self.hook_keyframe
|
c.hook_keyframe = self.hook_keyframe
|
||||||
return c
|
return c
|
||||||
|
|
||||||
def __eq__(self, other: 'HookWeight'):
|
def __eq__(self, other: 'Hook'):
|
||||||
return self.__class__ == other.__class__ and self.hook_ref == other.hook_ref
|
return self.__class__ == other.__class__ and self.hook_ref == other.hook_ref
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.hook_ref)
|
return hash(self.hook_ref)
|
||||||
|
|
||||||
class HookWeightGroup:
|
class HookGroup:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.hooks: List[HookWeight] = []
|
self.hooks: List[Hook] = []
|
||||||
|
|
||||||
def add(self, hook: HookWeight):
|
def add(self, hook: Hook):
|
||||||
if hook not in self.hooks:
|
if hook not in self.hooks:
|
||||||
self.hooks.append(hook)
|
self.hooks.append(hook)
|
||||||
|
|
||||||
def contains(self, hook: HookWeight):
|
def contains(self, hook: Hook):
|
||||||
return hook in self.hooks
|
return hook in self.hooks
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
c = HookWeightGroup()
|
c = HookGroup()
|
||||||
# TODO: review if clone is necessary
|
# TODO: review if clone is necessary
|
||||||
for hook in self.hooks:
|
for hook in self.hooks:
|
||||||
c.add(hook.clone())
|
c.add(hook.clone())
|
||||||
return c
|
return c
|
||||||
|
|
||||||
def clone_and_combine(self, other: 'HookWeightGroup'):
|
def clone_and_combine(self, other: 'HookGroup'):
|
||||||
c = self.clone()
|
c = self.clone()
|
||||||
for hook in other.hooks:
|
for hook in other.hooks:
|
||||||
c.add(hook.clone())
|
c.add(hook.clone())
|
||||||
@ -69,8 +75,8 @@ class HookWeightGroup:
|
|||||||
hook.hook_keyframe = hook_kf
|
hook.hook_keyframe = hook_kf
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def combine_all_hooks(hooks_list: List['HookWeightGroup'], require_count=1) -> 'HookWeightGroup':
|
def combine_all_hooks(hooks_list: List['HookGroup'], require_count=1) -> 'HookGroup':
|
||||||
actual: List[HookWeightGroup] = []
|
actual: List[HookGroup] = []
|
||||||
for group in hooks_list:
|
for group in hooks_list:
|
||||||
if group is not None:
|
if group is not None:
|
||||||
actual.append(group)
|
actual.append(group)
|
||||||
@ -79,7 +85,7 @@ class HookWeightGroup:
|
|||||||
# if only 1 hook, just reutnr itself without cloning
|
# if only 1 hook, just reutnr itself without cloning
|
||||||
if len(actual) == 1:
|
if len(actual) == 1:
|
||||||
return actual[0]
|
return actual[0]
|
||||||
final_hook: HookWeightGroup = None
|
final_hook: HookGroup = None
|
||||||
for hook in actual:
|
for hook in actual:
|
||||||
if final_hook is None:
|
if final_hook is None:
|
||||||
final_hook = hook.clone()
|
final_hook = hook.clone()
|
||||||
@ -203,3 +209,149 @@ def get_sorted_list_via_attr(objects: List, attr: str) -> List:
|
|||||||
for object_list in sorted_attrs.values():
|
for object_list in sorted_attrs.values():
|
||||||
sorted_list.extend(object_list)
|
sorted_list.extend(object_list)
|
||||||
return sorted_list
|
return sorted_list
|
||||||
|
|
||||||
|
|
||||||
|
def load_hook_lora_for_models(model: 'ModelPatcher', clip: 'CLIP', lora: Dict[str, torch.Tensor],
|
||||||
|
hook: Hook, strength_model: float, strength_clip: float):
|
||||||
|
key_map = {}
|
||||||
|
if model is not None:
|
||||||
|
key_map = comfy.lora.model_lora_keys_unet(model.model, key_map)
|
||||||
|
if clip is not None:
|
||||||
|
key_map = comfy.lora.model_lora_keys_clip(clip.cond_stage_model, key_map)
|
||||||
|
|
||||||
|
loaded: Dict[str] = comfy.lora.load_lora(lora, key_map)
|
||||||
|
if model is not None:
|
||||||
|
new_modelpatcher = model.clone()
|
||||||
|
k = new_modelpatcher.add_hook_patches(hook=hook, patches=loaded, strength_patch=strength_model)
|
||||||
|
else:
|
||||||
|
k = ()
|
||||||
|
new_modelpatcher = None
|
||||||
|
|
||||||
|
# TODO: make hooks work with clip
|
||||||
|
if clip is not None:
|
||||||
|
new_clip = clip.clone()
|
||||||
|
k1 = []
|
||||||
|
#k1 = new_clip.add_hook_patches(hook=hook, patches=loaded, strength_patch=strength_clip)
|
||||||
|
else:
|
||||||
|
k1 = ()
|
||||||
|
new_clip = None
|
||||||
|
k = set(k)
|
||||||
|
k1 = set(k1)
|
||||||
|
for x in loaded:
|
||||||
|
if (x not in k) and (x not in k1):
|
||||||
|
print(f"NOT LOADED {x}")
|
||||||
|
return (new_modelpatcher, new_clip)
|
||||||
|
|
||||||
|
def load_hook_model_as_lora_for_models(model: 'ModelPatcher', clip: 'CLIP',
|
||||||
|
model_loaded: 'ModelPatcher', clip_loaded: 'CLIP',
|
||||||
|
hook: Hook, strength_model: float, strength_clip: float):
|
||||||
|
if model is not None and model_loaded is not None:
|
||||||
|
new_modelpatcher = model.clone()
|
||||||
|
comfy.model_management.unload_model_clones(new_modelpatcher)
|
||||||
|
expected_model_keys = set(model_loaded.model.state_dict().keys())
|
||||||
|
patches_model: Dict[str, torch.Tensor] = model_loaded.model.state_dict()
|
||||||
|
# do not include ANY model_sampling components of the model that should act as a patch
|
||||||
|
for key in list(patches_model.keys()):
|
||||||
|
if key.startswith("model_sampling"):
|
||||||
|
expected_model_keys.discard(key)
|
||||||
|
patches_model.pop(key, None)
|
||||||
|
k = new_modelpatcher.add_hook_patches(hook=hook, patches=patches_model, strength_patch=strength_model, is_diff=True)
|
||||||
|
else:
|
||||||
|
k = ()
|
||||||
|
new_modelpatcher = None
|
||||||
|
|
||||||
|
# TODO: make hooks work with clip
|
||||||
|
if clip is not None and clip_loaded is not None:
|
||||||
|
new_clip = clip.clone()
|
||||||
|
comfy.model_management.unload_model_clones(new_clip.patcher)
|
||||||
|
expected_clip_keys = clip_loaded.patcher.model.state_dict().copy()
|
||||||
|
patches_clip: Dict[str, torch.Tensor] = clip_loaded.cond_stage_model.state_dict()
|
||||||
|
k1 = []
|
||||||
|
#k1 = new_clip.add_hook_patches(hook=hook, patches=patches_clip, strength_patch=strength_clip, is_diff=True)
|
||||||
|
else:
|
||||||
|
k1 = ()
|
||||||
|
new_clip = None
|
||||||
|
|
||||||
|
k = set(k)
|
||||||
|
k1 = set(k1)
|
||||||
|
if model is not None and model_loaded is not None:
|
||||||
|
for key in expected_model_keys:
|
||||||
|
if key not in k:
|
||||||
|
print(f"MODEL-AS-LORA NOT LOADED {key}")
|
||||||
|
if clip is not None and clip_loaded is not None:
|
||||||
|
for key in expected_clip_keys:
|
||||||
|
if key not in k1:
|
||||||
|
print(f"CLIP-AS-LORA NOT LOADED {key}")
|
||||||
|
|
||||||
|
return (new_modelpatcher, new_clip)
|
||||||
|
|
||||||
|
def set_hooks_for_conditioning(cond, hooks: HookGroup):
|
||||||
|
if hooks is None:
|
||||||
|
return cond
|
||||||
|
return conditioning_set_values(cond, {'hooks': hooks})
|
||||||
|
|
||||||
|
def set_timesteps_for_conditioning(cond, timestep_range: Tuple[float,float]):
|
||||||
|
if timestep_range is None:
|
||||||
|
return cond
|
||||||
|
return conditioning_set_values(cond, {"start_percent": timestep_range[0],
|
||||||
|
"end_percent": timestep_range[1]})
|
||||||
|
|
||||||
|
def set_mask_for_conditioning(cond, mask: torch.Tensor, set_cond_area: str, strength: float):
|
||||||
|
if mask is None:
|
||||||
|
return cond
|
||||||
|
set_area_to_bounds = False
|
||||||
|
if set_cond_area != 'default':
|
||||||
|
set_area_to_bounds = True
|
||||||
|
if len(mask.shape) < 3:
|
||||||
|
mask = mask.unsqueeze(0)
|
||||||
|
return conditioning_set_values(cond, {'mask': mask,
|
||||||
|
'set_area_to_bounds': set_area_to_bounds,
|
||||||
|
'mask_strength': strength})
|
||||||
|
|
||||||
|
def combine_conditioning(conds: List):
|
||||||
|
combined_conds = []
|
||||||
|
for cond in conds:
|
||||||
|
combined_conds.extend(cond)
|
||||||
|
return combined_conds
|
||||||
|
|
||||||
|
def set_mask_conds(conds: List, strength: float, set_cond_area: str,
|
||||||
|
opt_mask: torch.Tensor=None, opt_hooks: HookGroup=None, opt_timestep_range: Tuple[float,float]=None):
|
||||||
|
masked_conds = []
|
||||||
|
for c in conds:
|
||||||
|
# first, apply lora_hook to conditioning, if provided
|
||||||
|
c = set_hooks_for_conditioning(c, opt_hooks)
|
||||||
|
# next, apply mask to conditioning
|
||||||
|
c = set_mask_for_conditioning(cond=c, mask=opt_mask, strength=strength, set_cond_area=set_cond_area)
|
||||||
|
# apply timesteps, if present
|
||||||
|
c = set_timesteps_for_conditioning(cond=c, timestep_range=opt_timestep_range)
|
||||||
|
# finally, apply mask to conditioning and store
|
||||||
|
masked_conds.append(c)
|
||||||
|
return masked_conds
|
||||||
|
|
||||||
|
def set_mask_and_combine_conds(conds: List, new_conds: List, strength: float=1.0, set_cond_area: str="default",
|
||||||
|
opt_mask: torch.Tensor=None, opt_hooks: HookGroup=None, opt_timestep_range: Tuple[float,float]=None):
|
||||||
|
combined_conds = []
|
||||||
|
for c, masked_c in zip(conds, new_conds):
|
||||||
|
# first, apply lora_hook to new conditioning, if provided
|
||||||
|
masked_c = set_hooks_for_conditioning(masked_c, opt_hooks)
|
||||||
|
# next, apply mask to new conditioning, if provided
|
||||||
|
masked_c = set_mask_for_conditioning(cond=masked_c, mask=opt_mask, set_cond_area=set_cond_area, strength=strength)
|
||||||
|
# apply timesteps, if present
|
||||||
|
masked_c = set_timesteps_for_conditioning(cond=masked_c, timestep_range=opt_timestep_range)
|
||||||
|
# finally, combine with existing conditioning and store
|
||||||
|
combined_conds.append(combine_conditioning([c, masked_c]))
|
||||||
|
return combined_conds
|
||||||
|
|
||||||
|
def set_default_and_combine_conds(conds: list, new_conds: list,
|
||||||
|
opt_hooks: HookGroup=None, opt_timestep_range: Tuple[float,float]=None):
|
||||||
|
combined_conds = []
|
||||||
|
for c, new_c in zip(conds, new_conds):
|
||||||
|
# first, apply lora_hook to new conditioning, if provided
|
||||||
|
new_c = set_hooks_for_conditioning(new_c, opt_hooks)
|
||||||
|
# next, add default_cond key to cond so that during sampling, it can be identified
|
||||||
|
new_c = conditioning_set_values(new_c, {'default': True})
|
||||||
|
# apply timesteps, if present
|
||||||
|
new_c = set_timesteps_for_conditioning(cond=new_c, timestep_range=opt_timestep_range)
|
||||||
|
# finally, combine with existing conditioning and store
|
||||||
|
combined_conds.append(combine_conditioning([c, new_c]))
|
||||||
|
return combined_conds
|
||||||
|
|||||||
@ -115,8 +115,8 @@ class ModelPatcher:
|
|||||||
|
|
||||||
self.hook_patches: Dict[comfy.hooks.HookRef] = {}
|
self.hook_patches: Dict[comfy.hooks.HookRef] = {}
|
||||||
self.hook_backup: Dict[str, Tuple[torch.Tensor, torch.device]] = {}
|
self.hook_backup: Dict[str, Tuple[torch.Tensor, torch.device]] = {}
|
||||||
self.cached_hook_patches: Dict[comfy.hooks.HookWeightGroup, Dict[str, torch.Tensor]] = {}
|
self.cached_hook_patches: Dict[comfy.hooks.HookGroup, Dict[str, torch.Tensor]] = {}
|
||||||
self.current_hooks: Optional[comfy.hooks.HookWeightGroup] = None
|
self.current_hooks: Optional[comfy.hooks.HookGroup] = None
|
||||||
# TODO: hook_mode should be entirely removed; behavior should be determined by remaining VRAM/memory
|
# TODO: hook_mode should be entirely removed; behavior should be determined by remaining VRAM/memory
|
||||||
self.hook_mode = comfy.hooks.EnumHookMode.MaxSpeed
|
self.hook_mode = comfy.hooks.EnumHookMode.MaxSpeed
|
||||||
|
|
||||||
@ -567,7 +567,7 @@ class ModelPatcher:
|
|||||||
def set_hook_mode(self, hook_mode: comfy.hooks.EnumHookMode):
|
def set_hook_mode(self, hook_mode: comfy.hooks.EnumHookMode):
|
||||||
self.hook_mode = hook_mode
|
self.hook_mode = hook_mode
|
||||||
|
|
||||||
def prepare_hook_patches_current_keyframe(self, t: torch.Tensor, hook_group: comfy.hooks.HookWeightGroup):
|
def prepare_hook_patches_current_keyframe(self, t: torch.Tensor, hook_group: comfy.hooks.HookGroup):
|
||||||
curr_t = t[0]
|
curr_t = t[0]
|
||||||
for hook in hook_group.hooks:
|
for hook in hook_group.hooks:
|
||||||
changed = hook.hook_keyframe.prepare_current_keyframe(curr_t=curr_t)
|
changed = hook.hook_keyframe.prepare_current_keyframe(curr_t=curr_t)
|
||||||
@ -584,7 +584,7 @@ class ModelPatcher:
|
|||||||
if cached_group.contains(hook):
|
if cached_group.contains(hook):
|
||||||
self.cached_hook_patches.pop(cached_group)
|
self.cached_hook_patches.pop(cached_group)
|
||||||
|
|
||||||
def add_hook_patches(self, hook: comfy.hooks.HookWeight, patches, strength_patch=1.0, strength_model=1.0, is_diff=False):
|
def add_hook_patches(self, hook: comfy.hooks.Hook, patches, strength_patch=1.0, strength_model=1.0, is_diff=False):
|
||||||
# NOTE: this mirrors behavior of add_patches func
|
# NOTE: this mirrors behavior of add_patches func
|
||||||
current_hook_patches: Dict[str,List] = self.hook_patches.get(hook.hook_ref, {})
|
current_hook_patches: Dict[str,List] = self.hook_patches.get(hook.hook_ref, {})
|
||||||
p = set()
|
p = set()
|
||||||
@ -620,7 +620,7 @@ class ModelPatcher:
|
|||||||
self.patches_uuid = uuid.uuid4()
|
self.patches_uuid = uuid.uuid4()
|
||||||
return list(p)
|
return list(p)
|
||||||
|
|
||||||
def get_combined_hook_patches(self, hooks: comfy.hooks.HookWeightGroup):
|
def get_combined_hook_patches(self, hooks: comfy.hooks.HookGroup):
|
||||||
# combined_patches will contain weights of all relevant hooks, per key
|
# combined_patches will contain weights of all relevant hooks, per key
|
||||||
combined_patches = {}
|
combined_patches = {}
|
||||||
if hooks is not None:
|
if hooks is not None:
|
||||||
@ -633,18 +633,18 @@ class ModelPatcher:
|
|||||||
else:
|
else:
|
||||||
# patches are stored as tuples: (strength_patch, (tuple_with_weights,), strength_model)
|
# patches are stored as tuples: (strength_patch, (tuple_with_weights,), strength_model)
|
||||||
for patch in hook_patches[key]:
|
for patch in hook_patches[key]:
|
||||||
new_patch = List(patch)
|
new_patch = list(patch)
|
||||||
new_patch[0] *= hook.strength
|
new_patch[0] *= hook.strength
|
||||||
current_patches.append(Tuple(new_patch))
|
current_patches.append(tuple(new_patch))
|
||||||
combined_patches[key] = current_patches
|
combined_patches[key] = current_patches
|
||||||
return combined_patches
|
return combined_patches
|
||||||
|
|
||||||
def apply_hooks(self, hooks: comfy.hooks.HookWeightGroup):
|
def apply_hooks(self, hooks: comfy.hooks.HookGroup):
|
||||||
if self.current_hooks == hooks:
|
if self.current_hooks == hooks:
|
||||||
return
|
return
|
||||||
self.patch_hooks(hooks=hooks)
|
self.patch_hooks(hooks=hooks)
|
||||||
|
|
||||||
def patch_hooks(self, hooks: comfy.hooks.HookWeightGroup):
|
def patch_hooks(self, hooks: comfy.hooks.HookGroup):
|
||||||
self.unpatch_hooks()
|
self.unpatch_hooks()
|
||||||
model_sd = self.model_state_dict()
|
model_sd = self.model_state_dict()
|
||||||
# if have cached weights for hooks, use it
|
# if have cached weights for hooks, use it
|
||||||
@ -680,7 +680,7 @@ class ModelPatcher:
|
|||||||
self.cached_hook_patches.clear()
|
self.cached_hook_patches.clear()
|
||||||
self.current_hooks = None
|
self.current_hooks = None
|
||||||
|
|
||||||
def patch_hook_weight_to_device(self, hooks: comfy.hooks.HookWeightGroup, combined_patches: dict, key: str):
|
def patch_hook_weight_to_device(self, hooks: comfy.hooks.HookGroup, combined_patches: dict, key: str):
|
||||||
if key not in combined_patches:
|
if key not in combined_patches:
|
||||||
return
|
return
|
||||||
weight: torch.Tensor = comfy.utils.get_attr(self.model, key)
|
weight: torch.Tensor = comfy.utils.get_attr(self.model, key)
|
||||||
@ -719,7 +719,7 @@ class ModelPatcher:
|
|||||||
comfy.utils.copy_to_param(self.model, k, self.hook_backup[k][0].to(device=self.hook_backup[k][1]))
|
comfy.utils.copy_to_param(self.model, k, self.hook_backup[k][0].to(device=self.hook_backup[k][1]))
|
||||||
|
|
||||||
self.hook_backup.clear()
|
self.hook_backup.clear()
|
||||||
self.current_hooks = None # TODO: should this be clear_cached_hook_weights instead?
|
self.current_hooks = None
|
||||||
|
|
||||||
def clean_hooks(self):
|
def clean_hooks(self):
|
||||||
self.unpatch_hooks()
|
self.unpatch_hooks()
|
||||||
|
|||||||
@ -140,7 +140,7 @@ def cond_cat(c_list):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def finalize_default_conds(hooked_to_run: Dict[comfy.hooks.HookWeightGroup,List[Tuple[Tuple,int]]], default_conds: List[List[Dict]], x_in, timestep):
|
def finalize_default_conds(hooked_to_run: Dict[comfy.hooks.HookGroup,List[Tuple[Tuple,int]]], default_conds: List[List[Dict]], x_in, timestep):
|
||||||
# need to figure out remaining unmasked area for conds
|
# need to figure out remaining unmasked area for conds
|
||||||
default_mults = []
|
default_mults = []
|
||||||
for _ in default_conds:
|
for _ in default_conds:
|
||||||
@ -171,7 +171,7 @@ def finalize_default_conds(hooked_to_run: Dict[comfy.hooks.HookWeightGroup,List[
|
|||||||
continue
|
continue
|
||||||
# replace p's mult with calculated mult
|
# replace p's mult with calculated mult
|
||||||
p = p._replace(mult=mult)
|
p = p._replace(mult=mult)
|
||||||
hook: comfy.hooks.HookWeightGroup = x.get('hook', None)
|
hook: comfy.hooks.HookGroup = x.get('hook', None)
|
||||||
hooked_to_run.setdefault(hook, list())
|
hooked_to_run.setdefault(hook, list())
|
||||||
hooked_to_run[hook] += [(p, i)]
|
hooked_to_run[hook] += [(p, i)]
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ def calc_cond_batch(model, conds: List[List[Dict]], x_in, timestep, model_option
|
|||||||
out_counts = []
|
out_counts = []
|
||||||
# separate conds by matching hooks
|
# separate conds by matching hooks
|
||||||
# TODO: implement default_conds support
|
# TODO: implement default_conds support
|
||||||
hooked_to_run: Dict[comfy.hooks.HookWeightGroup,List[Tuple[Tuple,int]]] = {}
|
hooked_to_run: Dict[comfy.hooks.HookGroup,List[Tuple[Tuple,int]]] = {}
|
||||||
default_conds = []
|
default_conds = []
|
||||||
has_default_conds = False
|
has_default_conds = False
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ def calc_cond_batch(model, conds: List[List[Dict]], x_in, timestep, model_option
|
|||||||
p = comfy.samplers.get_area_and_mult(x, x_in, timestep)
|
p = comfy.samplers.get_area_and_mult(x, x_in, timestep)
|
||||||
if p is None:
|
if p is None:
|
||||||
continue
|
continue
|
||||||
hooks: comfy.hooks.HookWeightGroup = x.get('hooks', None)
|
hooks: comfy.hooks.HookGroup = x.get('hooks', None)
|
||||||
if hooks is not None:
|
if hooks is not None:
|
||||||
model.current_patcher.prepare_hook_patches_current_keyframe(timestep, hooks)
|
model.current_patcher.prepare_hook_patches_current_keyframe(timestep, hooks)
|
||||||
hooked_to_run.setdefault(hooks, list())
|
hooked_to_run.setdefault(hooks, list())
|
||||||
|
|||||||
432
comfy_extras/nodes_hooks.py
Normal file
432
comfy_extras/nodes_hooks.py
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||||
|
import torch
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from comfy.model_patcher import ModelPatcher
|
||||||
|
from comfy.sd import CLIP
|
||||||
|
|
||||||
|
import comfy.hooks
|
||||||
|
import comfy.sd
|
||||||
|
import folder_paths
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
# Mask, Combine, and Hook Conditioning
|
||||||
|
#------------------------------------------
|
||||||
|
class PairConditioningSetProperties:
|
||||||
|
NodeId = 'PairConditioningSetProperties'
|
||||||
|
NodeName = 'Pair Cond Set Props'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"positive_NEW": ("CONDITIONING", ),
|
||||||
|
"negative_NEW": ("CONDITIONING", ),
|
||||||
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"set_cond_area": (["default", "mask bounds"],),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"opt_mask": ("MASK", ),
|
||||||
|
"opt_hooks": ("HOOKS",),
|
||||||
|
"opt_timesteps": ("TIMESTEPS_RANGE",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
|
||||||
|
RETURN_NAMES = ("positive", "negative")
|
||||||
|
CATEGORY = "advanced/hooks/cond pair"
|
||||||
|
FUNCTION = "set_properties"
|
||||||
|
|
||||||
|
def set_properties(self, positive_NEW, negative_NEW,
|
||||||
|
strength: float, set_cond_area: str,
|
||||||
|
opt_mask: torch.Tensor=None, opt_hooks: comfy.hooks.Hook=None, opt_timesteps: Tuple=None):
|
||||||
|
final_positive, final_negative = comfy.hooks.set_mask_conds(conds=[positive_NEW, negative_NEW],
|
||||||
|
strength=strength, set_cond_area=set_cond_area,
|
||||||
|
opt_mask=opt_mask, opt_hooks=opt_hooks, opt_timestep_range=opt_timesteps)
|
||||||
|
return (final_positive, final_negative)
|
||||||
|
|
||||||
|
class ConditioningSetProperties:
|
||||||
|
NodeId = 'ConditioningSetProperties'
|
||||||
|
NodeName = 'Cond Set Props'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"cond_NEW": ("CONDITIONING", ),
|
||||||
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"set_cond_area": (["default", "mask bounds"],),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"opt_mask": ("MASK", ),
|
||||||
|
"opt_hooks": ("HOOKS",),
|
||||||
|
"opt_timesteps": ("TIMESTEPS_RANGE",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
|
RETURN_NAMES = ("positive", "negative")
|
||||||
|
CATEGORY = "advanced/hooks/cond single"
|
||||||
|
FUNCTION = "set_properties"
|
||||||
|
|
||||||
|
def set_properties(self, cond_NEW,
|
||||||
|
strength: float, set_cond_area: str,
|
||||||
|
opt_mask: torch.Tensor=None, opt_hooks: comfy.hooks.Hook=None, opt_timesteps: Tuple=None):
|
||||||
|
(final_cond,) = comfy.hooks.set_mask_conds(conds=[cond_NEW],
|
||||||
|
strength=strength, set_cond_area=set_cond_area,
|
||||||
|
opt_mask=opt_mask, opt_hooks=opt_hooks, opt_timestep_range=opt_timesteps)
|
||||||
|
return (final_cond,)
|
||||||
|
|
||||||
|
class PairConditioningCombine:
|
||||||
|
NodeId = 'PairConditioningCombine'
|
||||||
|
NodeName = 'Pair Cond Combine'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"positive_A": ("CONDITIONING",),
|
||||||
|
"negative_A": ("CONDITIONING",),
|
||||||
|
"positive_B": ("CONDITIONING",),
|
||||||
|
"negative_B": ("CONDITIONING",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
|
||||||
|
RETURN_NAMES = ("positive", "negative")
|
||||||
|
CATEGORY = "advanced/hooks/cond pair"
|
||||||
|
FUNCTION = "combine"
|
||||||
|
|
||||||
|
def combine(self, positive_A, negative_A, positive_B, negative_B):
|
||||||
|
final_positive, final_negative = comfy.hooks.set_mask_and_combine_conds(conds=[positive_A, negative_A], new_conds=[positive_B, negative_B],)
|
||||||
|
return (final_positive, final_negative,)
|
||||||
|
|
||||||
|
class PairConditioningSetDefaultAndCombine:
|
||||||
|
NodeId = 'PairConditioningSetDefaultCombine'
|
||||||
|
NodeName = 'Pair Cond Set Default Combine'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"positive": ("CONDITIONING",),
|
||||||
|
"negative": ("CONDITIONING",),
|
||||||
|
"positive_DEFAULT": ("CONDITIONING",),
|
||||||
|
"negative_DEFAULT": ("CONDITIONING",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"opt_hooks": ("HOOKS",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
|
||||||
|
RETURN_NAMES = ("positive", "negative")
|
||||||
|
CATEGORY = "advanced/hooks/cond pair"
|
||||||
|
FUNCTION = "set_default_and_combine"
|
||||||
|
|
||||||
|
def set_default_and_combine(self, positive, negative, positive_DEFAULT, negative_DEFAULT,
|
||||||
|
opt_hooks: comfy.hooks.HookGroup=None):
|
||||||
|
final_positive, final_negative = comfy.hooks.set_default_and_combine_conds(conds=[positive, negative], new_conds=[positive_DEFAULT, negative_DEFAULT],
|
||||||
|
opt_hooks=opt_hooks)
|
||||||
|
return (final_positive, final_negative)
|
||||||
|
|
||||||
|
class ConditioningSetDefaultAndCombine:
|
||||||
|
NodeId = 'ConditioningSetDefaultCombine'
|
||||||
|
NodeName = 'Cond Set Default Combine'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"cond": ("CONDITIONING",),
|
||||||
|
"cond_DEFAULT": ("CONDITIONING",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"opt_hooks": ("HOOKS",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
|
CATEGORY = "advanced/hooks/cond single"
|
||||||
|
FUNCTION = "set_default_and_combine"
|
||||||
|
|
||||||
|
def append_and_combine(self, cond, cond_DEFAULT,
|
||||||
|
opt_hooks: comfy.hooks.HookGroup=None):
|
||||||
|
(final_conditioning,) = comfy.hooks.set_default_and_combine_conds(conds=[cond], new_conds=[cond_DEFAULT],
|
||||||
|
opt_hooks=opt_hooks)
|
||||||
|
return (final_conditioning,)
|
||||||
|
#------------------------------------------
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
# Register Hooks
|
||||||
|
#------------------------------------------
|
||||||
|
class RegisterHookLora:
|
||||||
|
NodeId = 'RegisterHookLora'
|
||||||
|
NodeName = 'Register Hook LoRA'
|
||||||
|
def __init__(self):
|
||||||
|
self.loaded_lora = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"model": ("MODEL",),
|
||||||
|
"clip": ("CLIP",),
|
||||||
|
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
||||||
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
"strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("MODEL", "CLIP", "HOOKS")
|
||||||
|
CATEGORY = "advanced/hooks/register"
|
||||||
|
FUNCTION = "register_lora"
|
||||||
|
|
||||||
|
def register_lora(self, model: 'ModelPatcher', clip: 'CLIP', lora_name: str,
|
||||||
|
strength_model: float, strength_clip: float):
|
||||||
|
if strength_model == 0 and strength_clip == 0:
|
||||||
|
return (model, clip, None)
|
||||||
|
|
||||||
|
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||||
|
lora = None
|
||||||
|
if self.loaded_lora is not None:
|
||||||
|
if self.loaded_lora[0] == lora_path:
|
||||||
|
lora = self.loaded_lora[1]
|
||||||
|
else:
|
||||||
|
temp = self.loaded_lora
|
||||||
|
self.loaded_lora = None
|
||||||
|
del temp
|
||||||
|
|
||||||
|
if lora is None:
|
||||||
|
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||||
|
self.loaded_lora = (lora_path, lora)
|
||||||
|
|
||||||
|
hook = comfy.hooks.Hook()
|
||||||
|
hook_group = comfy.hooks.HookGroup()
|
||||||
|
hook_group.add(hook)
|
||||||
|
model_lora, clip_lora = comfy.hooks.load_hook_lora_for_models(model=model, clip=clip, lora=lora, hook=hook,
|
||||||
|
strength_model=strength_model, strength_clip=strength_clip)
|
||||||
|
return (model_lora, clip_lora, hook_group)
|
||||||
|
|
||||||
|
class RegisterHookLoraModelOnly(RegisterHookLora):
|
||||||
|
NodeId = 'RegisterHookLoraModelOnly'
|
||||||
|
NodeName = 'Register Hook LoRA (MO)'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"model": ("MODEL",),
|
||||||
|
"clip": ("CLIP",),
|
||||||
|
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
||||||
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
"strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("MODEL", "HOOKS")
|
||||||
|
CATEGORY = "advanced/hooks/register"
|
||||||
|
FUNCTION = "register_lora_model_only"
|
||||||
|
|
||||||
|
def register_lora_model_only(self, model: 'ModelPatcher', lora_name: str, strength_model: float):
|
||||||
|
model_lora, _, hooks = self.register_lora(model=model, clip=None, lora_name=lora_name,
|
||||||
|
strength_model=strength_model, strength_clip=0)
|
||||||
|
return (model_lora, hooks)
|
||||||
|
|
||||||
|
class RegisterHookModelAsLora:
|
||||||
|
NodeId = 'RegisterHookModelAsLora'
|
||||||
|
NodeName = 'Register Hook Model as LoRA'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"model": ("MODEL",),
|
||||||
|
"clip": ("CLIP",),
|
||||||
|
"ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
|
||||||
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
"strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("MODEL", "CLIP", "HOOKS")
|
||||||
|
CATEGORY = "advanced/hooks/register"
|
||||||
|
FUNCTION = "register_model_as_lora"
|
||||||
|
|
||||||
|
def register_model_as_lora(self, model: 'ModelPatcher', clip: 'CLIP', ckpt_name: str,
|
||||||
|
strength_model: float, strength_clip: float):
|
||||||
|
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
||||||
|
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||||
|
model_loaded = out[0]
|
||||||
|
clip_loaded = out[1]
|
||||||
|
|
||||||
|
hook = comfy.hooks.Hook()
|
||||||
|
hook_group = comfy.hooks.HookGroup()
|
||||||
|
hook_group.add(hook)
|
||||||
|
model_lora, clip_lora = comfy.hooks.load_hook_model_as_lora_for_models(model=model, clip=clip,
|
||||||
|
model_loaded=model_loaded, clip_loaded=clip_loaded,
|
||||||
|
hook=hook,
|
||||||
|
strength_model=strength_model, strength_clip=strength_clip)
|
||||||
|
return (model_lora, clip_lora, hook_group)
|
||||||
|
|
||||||
|
class RegisterHookModelAsLoraModelOnly:
|
||||||
|
NodeId = 'RegisterHookModelAsLoraModelOnly'
|
||||||
|
NodeName = 'Register Hook Model as LoRA (MO)'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"model": ("MODEL",),
|
||||||
|
"ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
|
||||||
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("MODEL", "HOOKS")
|
||||||
|
CATEGORY = "advanced/hooks/register"
|
||||||
|
FUNCTION = "register_model_as_lora_model_only"
|
||||||
|
|
||||||
|
def register_model_as_lora_model_only(self, model: 'ModelPatcher', ckpt_name: str, strength_model: float):
|
||||||
|
model_lora, _, hooks = RegisterHookModelAsLora.register_model_as_lora(self, model=model, clip=None, ckpt_name=ckpt_name,
|
||||||
|
strength_model=strength_model, strength_clip=0)
|
||||||
|
return (model_lora, hooks)
|
||||||
|
#------------------------------------------
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
# Schedule Hooks
|
||||||
|
#------------------------------------------
|
||||||
|
#------------------------------------------
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
|
||||||
|
class SetModelHooksOnCond:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"conditioning": ("CONDITIONING",),
|
||||||
|
"hooks": ("HOOKS",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
|
CATEGORY = "advanced/hooks/manual"
|
||||||
|
FUNCTION = "attach_hook"
|
||||||
|
|
||||||
|
def attach_hook(self, conditioning, hooks: comfy.hooks.HookGroup):
|
||||||
|
return (comfy.hooks.set_hooks_for_conditioning(conditioning, hooks),)
|
||||||
|
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
# Combine Hooks
|
||||||
|
#------------------------------------------
|
||||||
|
class CombineHooks:
|
||||||
|
NodeId = 'CombineHooks2'
|
||||||
|
NodeName = 'Combine Hooks [2]'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"hooks_A": ("HOOKS",),
|
||||||
|
"hooks_B": ("HOOKS",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("HOOKS",)
|
||||||
|
CATEGORY = "advanced/hooks/combine"
|
||||||
|
FUNCTION = "combine_hooks"
|
||||||
|
|
||||||
|
def combine_hooks(self,
|
||||||
|
hooks_A: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_B: comfy.hooks.HookGroup=None):
|
||||||
|
candidates = [hooks_A, hooks_B]
|
||||||
|
return (comfy.hooks.HookGroup.combine_all_hooks(candidates),)
|
||||||
|
|
||||||
|
class CombineHooksFour:
|
||||||
|
NodeId = 'CombineHooks4'
|
||||||
|
NodeName = 'Combine Hooks [4]'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"hooks_A": ("HOOKS",),
|
||||||
|
"hooks_B": ("HOOKS",),
|
||||||
|
"hooks_C": ("HOOKS",),
|
||||||
|
"hooks_D": ("HOOKS",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("HOOKS",)
|
||||||
|
CATEGORY = "advanced/hooks/combine"
|
||||||
|
FUNCTION = "combine_hooks"
|
||||||
|
|
||||||
|
def combine_hooks(self,
|
||||||
|
hooks_A: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_B: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_C: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_D: comfy.hooks.HookGroup=None):
|
||||||
|
candidates = [hooks_A, hooks_B, hooks_C, hooks_D]
|
||||||
|
return (comfy.hooks.HookGroup.combine_all_hooks(candidates),)
|
||||||
|
|
||||||
|
class CombineHooksEight:
|
||||||
|
NodeId = 'CombineHooks8'
|
||||||
|
NodeName = 'Combine Hooks [8]'
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"hooks_A": ("HOOKS",),
|
||||||
|
"hooks_B": ("HOOKS",),
|
||||||
|
"hooks_C": ("HOOKS",),
|
||||||
|
"hooks_D": ("HOOKS",),
|
||||||
|
"hooks_E": ("HOOKS",),
|
||||||
|
"hooks_F": ("HOOKS",),
|
||||||
|
"hooks_G": ("HOOKS",),
|
||||||
|
"hooks_H": ("HOOKS",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("HOOKS",)
|
||||||
|
CATEGORY = "advanced/hooks/combine"
|
||||||
|
FUNCTION = "combine_hooks"
|
||||||
|
|
||||||
|
def combine_hooks(self,
|
||||||
|
hooks_A: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_B: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_C: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_D: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_E: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_F: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_G: comfy.hooks.HookGroup=None,
|
||||||
|
hooks_H: comfy.hooks.HookGroup=None):
|
||||||
|
candidates = [hooks_A, hooks_B, hooks_C, hooks_D, hooks_E, hooks_F, hooks_G, hooks_H]
|
||||||
|
return (comfy.hooks.HookGroup.combine_all_hooks(candidates),)
|
||||||
|
#------------------------------------------
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
node_list = [
|
||||||
|
# Register
|
||||||
|
RegisterHookLora,
|
||||||
|
RegisterHookLoraModelOnly,
|
||||||
|
RegisterHookModelAsLora,
|
||||||
|
RegisterHookModelAsLoraModelOnly,
|
||||||
|
# Combine
|
||||||
|
CombineHooks,
|
||||||
|
CombineHooksFour,
|
||||||
|
CombineHooksEight,
|
||||||
|
# Attach
|
||||||
|
ConditioningSetProperties,
|
||||||
|
PairConditioningSetProperties,
|
||||||
|
ConditioningSetDefaultAndCombine,
|
||||||
|
PairConditioningSetDefaultAndCombine,
|
||||||
|
PairConditioningCombine
|
||||||
|
]
|
||||||
|
NODE_CLASS_MAPPINGS = {}
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||||
|
|
||||||
|
for node in node_list:
|
||||||
|
NODE_CLASS_MAPPINGS[node.NodeId] = node
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS[node.NodeId] = node.NodeName
|
||||||
Loading…
x
Reference in New Issue
Block a user