Made all controlnets works with upcoming ComfyUI changes, started work on making latent preprocs obsolete, still need to make control weights apply the same way as before

This commit is contained in:
Jedrzej Kosinski
2024-06-27 05:03:10 -05:00
parent e6085cc111
commit e800d52acf
6 changed files with 104 additions and 102 deletions
+28 -25
View File
@@ -11,10 +11,10 @@ import comfy.controlnet as comfy_cn
from comfy.controlnet import ControlBase, ControlNet, ControlLora, T2IAdapter
from comfy.model_patcher import ModelPatcher
from .control_sparsectrl import SparseModelPatcher, SparseControlNet, SparseCtrlMotionWrapper, SparseMethod, SparseSettings, SparseSpreadMethod, PreprocSparseRGBWrapper, SparseConst
from .control_sparsectrl import SparseModelPatcher, SparseControlNet, SparseCtrlMotionWrapper, SparseSettings, SparseConst
from .control_lllite import LLLiteModule, LLLitePatch
from .control_svd import svd_unet_config_from_diffusers_unet, SVDControlNet, svd_unet_to_diffusers
from .utils import (AdvancedControlBase, TimestepKeyframeGroup, LatentKeyframeGroup, ControlWeightType, ControlWeights, WeightTypeException,
from .utils import (AdvancedControlBase, TimestepKeyframeGroup, LatentKeyframeGroup, AbstractPreprocWrapper, ControlWeightType, ControlWeights, WeightTypeException,
manual_cast_clean_groupnorm, disable_weight_init_clean_groupnorm, prepare_mask_batch, get_properly_arranged_t2i_weights, load_torch_file_with_dict_factory,
broadcast_image_to_extend, extend_to_batch_size)
from .logger import logger
@@ -81,7 +81,7 @@ class ControlNetAdvanced(ControlNet, AdvancedControlBase):
x_noisy = self.model_sampling_current.calculate_input(t, x_noisy)
control = self.control_model(x=x_noisy.to(dtype), hint=self.cond_hint, timesteps=timestep.float(), context=context.to(dtype), y=y)
return self.control_merge(None, control, control_prev, output_dtype)
return self.control_merge(control, control_prev, output_dtype)
def copy(self):
c = ControlNetAdvanced(self.control_model, self.timestep_keyframes, global_average_pooling=self.global_average_pooling, load_device=self.load_device, manual_cast_dtype=self.manual_cast_dtype)
@@ -100,22 +100,17 @@ class T2IAdapterAdvanced(T2IAdapter, AdvancedControlBase):
super().__init__(t2i_model=t2i_model, channels_in=channels_in, compression_ratio=compression_ratio, upscale_algorithm=upscale_algorithm, device=device)
AdvancedControlBase.__init__(self, super(), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.t2iadapter())
def control_merge_inject(self, control_input, control_output, control_prev, output_dtype):
def control_merge_inject(self, control: dict[str, list[Tensor]], control_prev, output_dtype):
# if has uncond multiplier, need to make sure control shapes are the same batch size as expected
if self.weights.has_uncond_multiplier or self.weights.has_uncond_mask:
if control_input is not None:
for i in range(len(control_input)):
x = control_input[i]
for key in control:
control_current = control[key]
for i in range(len(control_current)):
x = control_current[i]
if x is not None:
if x.size(0) < self.batch_size:
control_input[i] = x.repeat(self.batched_number, 1, 1, 1)[:self.batch_size]
if control_output is not None:
for i in range(len(control_output)):
x = control_output[i]
if x is not None:
if x.size(0) < self.batch_size:
control_output[i] = x.repeat(self.batched_number, 1, 1, 1)[:self.batch_size]
return AdvancedControlBase.control_merge_inject(self, control_input, control_output, control_prev, output_dtype)
control_current[i] = x.repeat(self.batched_number, 1, 1, 1)[:self.batch_size]
return AdvancedControlBase.control_merge_inject(self, control, control_prev, output_dtype)
def get_universal_weights(self) -> ControlWeights:
raw_weights = [(self.weights.base_multiplier ** float(7 - i)) for i in range(8)]
@@ -199,8 +194,8 @@ class SVDControlNetAdvanced(ControlNetAdvanced):
def __init__(self, control_model: SVDControlNet, timestep_keyframes: TimestepKeyframeGroup, global_average_pooling=False, device=None, load_device=None, manual_cast_dtype=None):
super().__init__(control_model=control_model, timestep_keyframes=timestep_keyframes, global_average_pooling=global_average_pooling, device=device, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
def set_cond_hint(self, *args, **kwargs):
to_return = super().set_cond_hint(*args, **kwargs)
def set_cond_hint_inject(self, *args, **kwargs):
to_return = super().set_cond_hint_inject(*args, **kwargs)
# cond hint for SVD-ControlNet needs to be scaled between (-1, 1) instead of (0, 1)
self.cond_hint_original = self.cond_hint_original * 2.0 - 1.0
return to_return
@@ -254,7 +249,7 @@ class SVDControlNetAdvanced(ControlNetAdvanced):
x_noisy = torch.cat([x_noisy] + [cond['c_concat']], dim=1)
control = self.control_model(x=x_noisy.to(dtype), hint=self.cond_hint, timesteps=timestep.float(), context=context.to(dtype), y=y, cond=cond)
return self.control_merge(None, control, control_prev, output_dtype)
return self.control_merge(control, control_prev, output_dtype)
def copy(self):
c = SVDControlNetAdvanced(self.control_model, self.timestep_keyframes, global_average_pooling=self.global_average_pooling, load_device=self.load_device, manual_cast_dtype=self.manual_cast_dtype)
@@ -269,6 +264,9 @@ class SparseCtrlAdvanced(ControlNetAdvanced):
self.control_model_wrapped = SparseModelPatcher(self.control_model, load_device=load_device, offload_device=comfy.model_management.unet_offload_device())
self.add_compatible_weight(ControlWeightType.SPARSECTRL)
self.control_model: SparseControlNet = self.control_model # does nothing except help with IDE hints
if self.control_model.use_simplified_conditioning_embedding:
self.require_vae = True
self.allow_condhint_latents = True
self.sparse_settings = sparse_settings if sparse_settings is not None else SparseSettings.default()
self.model_latent_format = None # latent format for active SD model, NOT controlnet
self.preprocessed = False
@@ -366,7 +364,7 @@ class SparseCtrlAdvanced(ControlNetAdvanced):
x_noisy = self.model_sampling_current.calculate_input(t, x_noisy)
control = self.control_model(x=x_noisy.to(dtype), hint=self.cond_hint, timesteps=timestep.float(), context=context.to(dtype), y=y)
return self.control_merge(None, control, control_prev, output_dtype)
return self.control_merge(control, control_prev, output_dtype)
def apply_advanced_strengths_and_masks(self, x: Tensor, batched_number: int):
# apply mults to indexes with and without a direct condhint
@@ -376,7 +374,7 @@ class SparseCtrlAdvanced(ControlNetAdvanced):
def pre_run_advanced(self, model, percent_to_timestep_function):
super().pre_run_advanced(model, percent_to_timestep_function)
if type(self.cond_hint_original) == PreprocSparseRGBWrapper:
if isinstance(self.cond_hint_original, AbstractPreprocWrapper):
if not self.control_model.use_simplified_conditioning_embedding:
raise ValueError("Any model besides RGB SparseCtrl should NOT have its images go through the RGB SparseCtrl preprocessor.")
self.cond_hint_original = self.cond_hint_original.condhint
@@ -415,8 +413,8 @@ class ControlLLLiteAdvanced(ControlBase, AdvancedControlBase):
model.set_model_attn1_patch(self.patch_attn1)
model.set_model_attn2_patch(self.patch_attn2)
def set_cond_hint(self, *args, **kwargs):
to_return = super().set_cond_hint(*args, **kwargs)
def set_cond_hint_inject(self, *args, **kwargs):
to_return = super().set_cond_hint_inject(*args, **kwargs)
# cond hint for LLLite needs to be scaled between (-1, 1) instead of (0, 1)
self.cond_hint_original = self.cond_hint_original * 2.0 - 1.0
return to_return
@@ -543,8 +541,6 @@ def load_controlnet(ckpt_path, timestep_keyframe: TimestepKeyframeGroup=None, mo
control = load_sparsectrl(ckpt_path, controlnet_data=controlnet_data, timestep_keyframe=timestep_keyframe, model=model)
elif controlnet_type == ControlWeightType.SVD_CONTROLNET:
control = load_svdcontrolnet(ckpt_path, controlnet_data=controlnet_data, timestep_keyframe=timestep_keyframe)
#raise Exception(f"SVD-ControlNet is not supported yet!")
#control = comfy_cn.load_controlnet(ckpt_path, model=model)
# otherwise, load vanilla ControlNet
else:
try:
@@ -563,7 +559,10 @@ def convert_to_advanced(control, timestep_keyframe: TimestepKeyframeGroup=None):
return control
# if exactly ControlNet returned, transform it into ControlNetAdvanced
if type(control) == ControlNet:
return ControlNetAdvanced.from_vanilla(v=control, timestep_keyframe=timestep_keyframe)
control = ControlNetAdvanced.from_vanilla(v=control, timestep_keyframe=timestep_keyframe)
if is_sd3_advanced_controlnet(control):
control.require_vae = True
return control
# if exactly ControlLora returned, transform it into ControlLoraAdvanced
elif type(control) == ControlLora:
return ControlLoraAdvanced.from_vanilla(v=control, timestep_keyframe=timestep_keyframe)
@@ -578,6 +577,10 @@ def is_advanced_controlnet(input_object):
return hasattr(input_object, "sub_idxs")
def is_sd3_advanced_controlnet(input_object: ControlNetAdvanced):
return type(input_object) == ControlNetAdvanced and input_object.latent_format is not None
def load_sparsectrl(ckpt_path: str, controlnet_data: dict[str, Tensor]=None, timestep_keyframe: TimestepKeyframeGroup=None, sparse_settings=SparseSettings.default(), model=None) -> SparseCtrlAdvanced:
if controlnet_data is None:
controlnet_data = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
+8 -8
View File
@@ -218,7 +218,7 @@ class ReferenceOptions:
class ReferencePreprocWrapper(AbstractPreprocWrapper):
error_msg = error_msg = "Invalid use of Reference Preprocess output. The output of RGB SparseCtrl preprocessor is NOT a usual image, but a latent pretending to be an image - you must connect the output directly to an Apply Advanced ControlNet node. It cannot be used for anything else that accepts IMAGE input."
error_msg = error_msg = "Invalid use of Reference Preprocess output. The output of Reference preprocessor is NOT a usual image, but a latent pretending to be an image - you must connect the output directly to an Apply Advanced ControlNet node. It cannot be used for anything else that accepts IMAGE input."
def __init__(self, condhint: Tensor):
super().__init__(condhint)
@@ -228,10 +228,10 @@ class ReferenceAdvanced(ControlBase, AdvancedControlBase):
def __init__(self, ref_opts: ReferenceOptions, timestep_keyframes: TimestepKeyframeGroup, device=None):
super().__init__(device)
AdvancedControlBase.__init__(self, super(), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.controllllite())
AdvancedControlBase.__init__(self, super(), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.controllllite(), allow_condhint_latents=True, require_vae=True)
self.ref_opts = ref_opts
self.order = 0
self.latent_format = None
self.model_latent_format = None
self.model_sampling_current = None
self.should_apply_attn_effective_strength = False
self.should_apply_adain_effective_strength = False
@@ -288,9 +288,9 @@ class ReferenceAdvanced(ControlBase, AdvancedControlBase):
def pre_run_advanced(self, model, percent_to_timestep_function):
AdvancedControlBase.pre_run_advanced(self, model, percent_to_timestep_function)
if type(self.cond_hint_original) == ReferencePreprocWrapper:
if isinstance(self.cond_hint_original, AbstractPreprocWrapper):
self.cond_hint_original = self.cond_hint_original.condhint
self.latent_format = model.latent_format # LatentFormat object, used to process_in latent cond_hint
self.model_latent_format = model.latent_format # LatentFormat object, used to process_in latent cond_hint
self.model_sampling_current = model.model_sampling
# SDXL is more sensitive to style_fidelity according to sd-webui-controlnet comments
if type(model).__name__ == "SDXL":
@@ -328,7 +328,7 @@ class ReferenceAdvanced(ControlBase, AdvancedControlBase):
if x_noisy.shape[0] != self.cond_hint.shape[0]:
self.cond_hint = broadcast_image_to_extend(self.cond_hint, x_noisy.shape[0], batched_number, except_one=False)
# noise cond_hint based on sigma (current step)
self.cond_hint = self.latent_format.process_in(self.cond_hint)
self.cond_hint = self.model_latent_format.process_in(self.cond_hint)
self.cond_hint = ref_noise_latents(self.cond_hint, sigma=t, noise=None)
timestep = self.model_sampling_current.timestep(t)
self.should_apply_attn_effective_strength = not (math.isclose(self.strength, 1.0) and math.isclose(self._current_timestep_keyframe.strength, 1.0) and math.isclose(self.ref_opts.attn_strength, 1.0))
@@ -343,8 +343,8 @@ class ReferenceAdvanced(ControlBase, AdvancedControlBase):
def cleanup_advanced(self):
super().cleanup_advanced()
del self.latent_format
self.latent_format = None
del self.model_latent_format
self.model_latent_format = None
del self.model_sampling_current
self.model_sampling_current = None
self.should_apply_attn_effective_strength = False
+10 -35
View File
@@ -23,13 +23,13 @@ from comfy.cldm.cldm import ControlNet as ControlNetCLDM
from comfy.ldm.modules.attention import SpatialTransformer
from comfy.ldm.modules.attention import attention_basic, attention_pytorch, attention_split, attention_sub_quad, default
from comfy.ldm.modules.attention import FeedForward, SpatialTransformer
from comfy.ldm.modules.diffusionmodules.openaimodel import TimestepEmbedSequential, ResBlock, Downsample
from comfy.ldm.modules.diffusionmodules.openaimodel import TimestepEmbedSequential
from comfy.model_patcher import ModelPatcher
import comfy.ops
import comfy.model_management
from .logger import logger
from .utils import (BIGMAX, TimestepKeyframeGroup, disable_weight_init_clean_groupnorm,
from .utils import (BIGMAX, AbstractPreprocWrapper, disable_weight_init_clean_groupnorm,
prepare_mask_batch, broadcast_image_to_extend, extend_to_batch_size)
@@ -85,7 +85,8 @@ class SparseControlNet(ControlNetCLDM):
x = torch.zeros_like(x)
guided_hint = self.input_hint_block(hint, emb, context)
outs = []
out_output = []
out_middle = []
hs = []
if self.num_classes is not None:
@@ -100,12 +101,12 @@ class SparseControlNet(ControlNetCLDM):
guided_hint = None
else:
h = module(h, emb, context)
outs.append(zero_conv(h, emb, context))
out_output.append(zero_conv(h, emb, context))
h = self.middle_block(h, emb, context)
outs.append(self.middle_block_out(h, emb, context))
out_middle.append(self.middle_block_out(h, emb, context))
return outs
return {"middle": out_middle, "output": out_output}
class SparseModelPatcher(ModelPatcher):
@@ -154,36 +155,10 @@ class SparseModelPatcher(ModelPatcher):
self.object_patches_backup = n.object_patches_backup
class PreprocSparseRGBWrapper:
error_msg = "Invalid use of RGB SparseCtrl output. The output of RGB SparseCtrl preprocessor is NOT a usual image, but a latent pretending to be an image - you must connect the output directly to an Apply ControlNet node (advanced or otherwise). It cannot be used for anything else that accepts IMAGE input."
class PreprocSparseRGBWrapper(AbstractPreprocWrapper):
error_msg = error_msg = "Invalid use of RGB SparseCtrl output. The output of RGB SparseCtrl preprocessor is NOT a usual image, but a latent pretending to be an image - you must connect the output directly to an Apply ControlNet node (advanced or otherwise). It cannot be used for anything else that accepts IMAGE input."
def __init__(self, condhint: Tensor):
self.condhint = condhint
def movedim(self, *args, **kwargs):
return self
def __getattr__(self, *args, **kwargs):
raise AttributeError(self.error_msg)
def __setattr__(self, name, value):
if name != "condhint":
raise AttributeError(self.error_msg)
super().__setattr__(name, value)
def __iter__(self, *args, **kwargs):
raise AttributeError(self.error_msg)
def __next__(self, *args, **kwargs):
raise AttributeError(self.error_msg)
def __len__(self, *args, **kwargs):
raise AttributeError(self.error_msg)
def __getitem__(self, *args, **kwargs):
raise AttributeError(self.error_msg)
def __setitem__(self, *args, **kwargs):
raise AttributeError(self.error_msg)
super().__init__(condhint)
class SparseContextAware:
+5 -4
View File
@@ -311,7 +311,8 @@ class SVDControlNet(nn.Module):
guided_hint = self.input_hint_block(hint, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator)
outs = []
out_output = []
out_middle = []
hs = []
if self.num_classes is not None:
@@ -326,12 +327,12 @@ class SVDControlNet(nn.Module):
guided_hint = None
else:
h = module(h, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator)
outs.append(zero_conv(h, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator))
out_output.append(zero_conv(h, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator))
h = self.middle_block(h, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator)
outs.append(self.middle_block_out(h, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator))
out_middle.append(self.middle_block_out(h, emb, context, time_context=time_context, num_video_frames=num_video_frames, image_only_indicator=image_only_indicator))
return outs
return {"middle": out_middle, "output": out_output}
TEMPORAL_TRANSFORMER_BLOCKS = {
+16 -4
View File
@@ -4,8 +4,8 @@ from torch import Tensor
import folder_paths
from comfy.model_patcher import ModelPatcher
from .control import load_controlnet, convert_to_advanced, is_advanced_controlnet
from .utils import ControlWeights, LatentKeyframeGroup, TimestepKeyframeGroup, BIGMAX
from .control import load_controlnet, convert_to_advanced, is_advanced_controlnet, is_sd3_advanced_controlnet
from .utils import ControlWeights, LatentKeyframeGroup, TimestepKeyframeGroup, AbstractPreprocWrapper, BIGMAX
from .nodes_weight import (DefaultWeights, ScaledSoftMaskedUniversalWeights, ScaledSoftUniversalWeights, SoftControlNetWeights, CustomControlNetWeights,
SoftT2IAdapterWeights, CustomT2IAdapterWeights)
from .nodes_keyframes import (LatentKeyframeGroupNode, LatentKeyframeInterpolationNode, LatentKeyframeBatchedGroupNode, LatentKeyframeNode,
@@ -89,6 +89,7 @@ class AdvancedControlNetApply:
"latent_kf_override": ("LATENT_KEYFRAME", ),
"weights_override": ("CONTROL_NET_WEIGHTS", ),
"model_optional": ("MODEL",),
"vae_optional": ("VAE",),
}
}
@@ -99,7 +100,7 @@ class AdvancedControlNetApply:
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝"
def apply_controlnet(self, positive, negative, control_net, image, strength, start_percent, end_percent,
mask_optional: Tensor=None, model_optional: ModelPatcher=None,
mask_optional: Tensor=None, model_optional: ModelPatcher=None, vae_optional=None,
timestep_kf: TimestepKeyframeGroup=None, latent_kf_override: LatentKeyframeGroup=None,
weights_override: ControlWeights=None):
if strength == 0:
@@ -121,7 +122,7 @@ class AdvancedControlNetApply:
c_net = cnets[prev_cnet]
else:
# copy, convert to advanced if needed, and set cond
c_net = convert_to_advanced(control_net.copy()).set_cond_hint(control_hint, strength, (start_percent, end_percent))
c_net = convert_to_advanced(control_net.copy()).set_cond_hint(control_hint, strength, (start_percent, end_percent), vae_optional)
if is_advanced_controlnet(c_net):
# disarm node check
c_net.disarm()
@@ -130,6 +131,17 @@ class AdvancedControlNetApply:
if not model_optional:
raise Exception(f"Type '{type(c_net).__name__}' requires model_optional input, but got None.")
c_net.patch_model(model=model_optional)
# if vae required, verify vae is passed in
if c_net.require_vae:
# if controlnet can accept preprocced condhint latents and is the case, ignore vae requirement
if c_net.allow_condhint_latents and isinstance(control_hint, AbstractPreprocWrapper):
pass
elif not vae_optional:
# make sure SD3 ControlNet will get a special message instead of generic type mention
if is_sd3_advanced_controlnet:
raise Exception(f"SD3 ControlNet requires vae_optional input, but got None.")
else:
raise Exception(f"Type '{type(c_net).__name__}' requires vae_optional input, but got None.")
# apply optional parameters and overrides, if provided
if timestep_kf is not None:
c_net.set_timestep_keyframes(timestep_kf)
+37 -26
View File
@@ -14,6 +14,7 @@ import comfy.model_base
from comfy.controlnet import ControlBase
from comfy.model_patcher import ModelPatcher
from comfy.sd import VAE
from .logger import logger
@@ -531,7 +532,7 @@ class WeightTypeException(TypeError):
class AdvancedControlBase:
def __init__(self, base: ControlBase, timestep_keyframes: TimestepKeyframeGroup, weights_default: ControlWeights, require_model=False):
def __init__(self, base: ControlBase, timestep_keyframes: TimestepKeyframeGroup, weights_default: ControlWeights, require_model=False, require_vae=False, allow_condhint_latents=False):
self.base = base
self.compatible_weights = [ControlWeightType.UNIVERSAL, ControlWeightType.DEFAULT]
self.add_compatible_weight(weights_default.weight_type)
@@ -564,8 +565,13 @@ class AdvancedControlBase:
self.pre_run = self.pre_run_inject
self.cleanup = self.cleanup_inject
self.set_previous_controlnet = self.set_previous_controlnet_inject
# require model to be passed into Apply Advanced ControlNet 🛂🅐🅒🅝 node
self.set_cond_hint = self.set_cond_hint_inject
# vae to store
self.adv_vae = None
# require model/vae to be passed into Apply Advanced ControlNet 🛂🅐🅒🅝 node
self.require_model = require_model
self.require_vae = require_vae
self.allow_condhint_latents = allow_condhint_latents
# disarm - when set to False, used to force usage of Apply Advanced ControlNet 🛂🅐🅒🅝 node (which will set it to True)
self.disarmed = not require_model
@@ -668,6 +674,21 @@ class AdvancedControlBase:
self.mask_cond_hint_original = mask_hint
return self
def set_cond_hint_inject(self, *args, **kwargs):
to_return = self.base.set_cond_hint(*args, **kwargs)
# if vae required, look in args and kwargs for it
if self.require_vae is not None:
# check args first, as that's the default way vae param is used in ComfyUI
for arg in args:
if isinstance(arg, VAE):
self.adv_vae = arg
break
# if not in args, check kwargs now
if self.adv_vae is None:
if 'vae' in kwargs:
self.adv_vae = kwargs['vae']
return to_return
def pre_run_inject(self, model, percent_to_timestep_function):
self.base.pre_run(model, percent_to_timestep_function)
self.pre_run_advanced(model, percent_to_timestep_function)
@@ -798,41 +819,28 @@ class AdvancedControlBase:
if self._current_timestep_keyframe.strength != 1.0:
x[:] *= self._current_timestep_keyframe.strength
def control_merge_inject(self: 'AdvancedControlBase', control_input, control_output, control_prev, output_dtype):
def control_merge_inject(self: 'AdvancedControlBase', control: dict[str, list[Tensor]], control_prev: dict, output_dtype):
out = {'input':[], 'middle':[], 'output': []}
if control_input is not None:
for i in range(len(control_input)):
key = 'input'
x = control_input[i]
if x is not None:
self.apply_advanced_strengths_and_masks(x, self.batched_number)
x *= self.strength * self.calc_weight(i, x, len(control_input))
if x.dtype != output_dtype:
x = x.to(output_dtype)
out[key].insert(0, x)
if control_output is not None:
for key in control:
control_output = control[key]
applied_to = set()
for i in range(len(control_output)):
if i == (len(control_output) - 1):
key = 'middle'
index = 0
else:
key = 'output'
index = i
x = control_output[i]
if x is not None:
self.apply_advanced_strengths_and_masks(x, self.batched_number)
if self.global_average_pooling:
x = torch.mean(x, dim=(2, 3), keepdim=True).repeat(1, 1, x.shape[2], x.shape[3])
x *= self.strength * self.calc_weight(i, x, len(control_output))
if x not in applied_to: #memory saving strategy, allow shared tensors and only apply strength to shared tensors once
applied_to.add(x)
self.apply_advanced_strengths_and_masks(x, self.batched_number)
x *= self.strength * self.calc_weight(i, x, len(control_output))
if x.dtype != output_dtype:
x = x.to(output_dtype)
out[key].append(x)
if control_prev is not None:
for x in ['input', 'middle', 'output']:
o = out[x]
@@ -847,7 +855,7 @@ class AdvancedControlBase:
if o[i].shape[0] < prev_val.shape[0]:
o[i] = prev_val + o[i]
else:
o[i] += prev_val
o[i] = prev_val + o[i] # TODO from base ComfyUI: change back to inplace add if shared tensors stop being an issue
return out
def prepare_mask_cond_hint(self, x_noisy: Tensor, t, cond, batched_number, dtype=None, direct_attn=False):
@@ -924,4 +932,7 @@ class AdvancedControlBase:
copied.mask_cond_hint_original = self.mask_cond_hint_original
copied.weights_override = self.weights_override
copied.latent_keyframe_override = self.latent_keyframe_override
copied.adv_vae = self.adv_vae
copied.require_vae = self.require_vae
copied.allow_condhint_latents = self.allow_condhint_latents
copied.disarmed = self.disarmed