Setting controlnet_config["dtype"] alone is not enough: comfy's lazy/zero-copy state_dict loading (Windows + aimdo path in disable_weight_init) assigns the on-disk fp32 tensors directly as parameters and ignores the configured dtype. With the disable_weight_init ops (no runtime weight casting), the control model then runs fp32 weights against fp16 activations, raising "mat1 and mat2 must have the same dtype, but got Half and Float" at the first time_embed Linear. Explicitly cast the control model to unet_dtype after load_state_dict (mirroring the motion model load and AnimateDiff-Evolved #573) so the weights always match the activation dtype at runtime. Verified end-to-end with v3_sd15_sparsectrl_rgb.ckpt on an fp16 SD1.5 model. Amp-Thread-ID: https://ampcode.com/threads/T-019e947a-9fd3-76df-a847-5eb68d7f18de Co-authored-by: Amp <amp@ampcode.com>
994 lines
52 KiB
Python
994 lines
52 KiB
Python
from typing import Callable, Union
|
|
from torch import Tensor
|
|
import torch
|
|
import os
|
|
|
|
import comfy.model_base
|
|
import comfy.ops
|
|
import comfy.utils
|
|
import comfy.model_management
|
|
import comfy.model_detection
|
|
import comfy.controlnet as comfy_cn
|
|
from comfy.controlnet import ControlBase, ControlNet, ControlNetSD35, ControlLora, T2IAdapter, StrengthType
|
|
from comfy.model_patcher import ModelPatcher
|
|
|
|
from .control_sparsectrl import SparseControlNet, SparseSettings, SparseConst, InterfaceAnimateDiffModel, create_sparse_modelpatcher, load_sparsectrl_motionmodel
|
|
from .control_lllite import LLLiteModule, LLLitePatch, load_controllllite
|
|
from .control_svd import svd_unet_config_from_diffusers_unet, SVDControlNet, svd_unet_to_diffusers
|
|
from .utils import (AdvancedControlBase, TimestepKeyframeGroup, LatentKeyframeGroup, AbstractPreprocWrapper, ControlWeightType, ControlWeights, WeightTypeException, Extras,
|
|
manual_cast_clean_groupnorm, disable_weight_init_clean_groupnorm, WrapperConsts, prepare_mask_batch, get_properly_arranged_t2i_weights, load_torch_file_with_dict_factory,
|
|
broadcast_image_to_extend, extend_to_batch_size, ORIG_PREVIOUS_CONTROLNET, CONTROL_INIT_BY_ACN)
|
|
from .logger import logger
|
|
|
|
|
|
class ControlNetAdvanced(ControlNet, AdvancedControlBase):
|
|
def __init__(self, control_model, timestep_keyframes: TimestepKeyframeGroup, global_average_pooling=False, compression_ratio=8, latent_format=None, load_device=None, manual_cast_dtype=None,
|
|
extra_conds=["y"], strength_type=StrengthType.CONSTANT, concat_mask=False, preprocess_image=lambda a: a):
|
|
super().__init__(control_model=control_model, global_average_pooling=global_average_pooling, compression_ratio=compression_ratio, latent_format=latent_format, load_device=load_device, manual_cast_dtype=manual_cast_dtype,
|
|
extra_conds=extra_conds, strength_type=strength_type, concat_mask=concat_mask, preprocess_image=preprocess_image)
|
|
AdvancedControlBase.__init__(self, super(type(self), self), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.controlnet())
|
|
self.is_flux = False
|
|
self.x_noisy_shape = None
|
|
|
|
def get_universal_weights(self) -> ControlWeights:
|
|
def cn_weights_func(idx: int, control: dict[str, list[Tensor]], key: str):
|
|
if key == "middle":
|
|
return 1.0 * self.weights.extras.get(Extras.MIDDLE_MULT, 1.0)
|
|
c_len = len(control[key])
|
|
raw_weights = [(self.weights.base_multiplier ** float((c_len) - i)) for i in range(c_len+1)]
|
|
raw_weights = raw_weights[:-1]
|
|
if key == "input":
|
|
raw_weights.reverse()
|
|
return raw_weights[idx]
|
|
return self.weights.copy_with_new_weights(new_weight_func=cn_weights_func)
|
|
|
|
def get_control_advanced(self, x_noisy, t, cond, batched_number, transformer_options):
|
|
# perform special version of get_control that supports sliding context and masks
|
|
return self.sliding_get_control(x_noisy, t, cond, batched_number, transformer_options)
|
|
|
|
def sliding_get_control(self, x_noisy: Tensor, t, cond, batched_number, transformer_options):
|
|
control_prev = None
|
|
if self.previous_controlnet is not None:
|
|
control_prev = self.previous_controlnet.get_control(x_noisy, t, cond, batched_number, transformer_options)
|
|
|
|
if self.timestep_range is not None:
|
|
if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]:
|
|
if control_prev is not None:
|
|
return control_prev
|
|
else:
|
|
return None
|
|
|
|
dtype = self.control_model.dtype
|
|
if self.manual_cast_dtype is not None:
|
|
dtype = self.manual_cast_dtype
|
|
|
|
# make cond_hint appropriate dimensions
|
|
# TODO: change this to not require cond_hint upscaling every step when self.sub_idxs are present
|
|
if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[2] * self.real_compression_ratio != self.cond_hint.shape[2] or x_noisy.shape[3] * self.real_compression_ratio != self.cond_hint.shape[3]:
|
|
if self.cond_hint is not None:
|
|
del self.cond_hint
|
|
self.cond_hint = None
|
|
self.real_compression_ratio = self.compression_ratio
|
|
compression_ratio = self.compression_ratio
|
|
if self.vae is not None and self.mult_by_ratio_when_vae:
|
|
compression_ratio *= self.vae.downscale_ratio
|
|
# if self.cond_hint_original length greater or equal to real latent count, subdivide it before scaling
|
|
if self.sub_idxs is not None:
|
|
actual_cond_hint_orig = self.cond_hint_original
|
|
if self.cond_hint_original.size(0) < self.full_latent_length:
|
|
actual_cond_hint_orig = extend_to_batch_size(tensor=actual_cond_hint_orig, batch_size=self.full_latent_length)
|
|
self.cond_hint = comfy.utils.common_upscale(actual_cond_hint_orig[self.sub_idxs], x_noisy.shape[3] * compression_ratio, x_noisy.shape[2] * compression_ratio, self.upscale_algorithm, "center")
|
|
else:
|
|
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original, x_noisy.shape[3] * compression_ratio, x_noisy.shape[2] * compression_ratio, self.upscale_algorithm, "center")
|
|
self.cond_hint = self.preprocess_image(self.cond_hint)
|
|
if self.vae is not None:
|
|
loaded_models = comfy.model_management.loaded_models(only_currently_used=True)
|
|
self.cond_hint = self.vae.encode(self.cond_hint.movedim(1, -1))
|
|
comfy.model_management.load_models_gpu(loaded_models)
|
|
if not self.mult_by_ratio_when_vae:
|
|
self.real_compression_ratio = 1
|
|
if self.latent_format is not None:
|
|
self.cond_hint = self.latent_format.process_in(self.cond_hint)
|
|
if len(self.extra_concat_orig) > 0:
|
|
to_concat = []
|
|
for c in self.extra_concat_orig:
|
|
c = c.to(self.cond_hint.device)
|
|
c = comfy.utils.common_upscale(c, self.cond_hint.shape[3], self.cond_hint.shape[2], self.upscale_algorithm, "center")
|
|
to_concat.append(comfy.utils.repeat_to_batch_size(c, self.cond_hint.shape[0]))
|
|
self.cond_hint = torch.cat([self.cond_hint] + to_concat, dim=1)
|
|
|
|
self.cond_hint = self.cond_hint.to(device=x_noisy.device, dtype=dtype)
|
|
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)
|
|
|
|
# prepare mask_cond_hint
|
|
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number, dtype=dtype)
|
|
|
|
context = cond.get('crossattn_controlnet', cond['c_crossattn'])
|
|
extra = self.extra_args.copy()
|
|
for c in self.extra_conds:
|
|
temp = cond.get(c, None)
|
|
if temp is not None:
|
|
extra[c] = comfy.model_base.convert_tensor(temp, dtype, x_noisy.device)
|
|
|
|
timestep = self.model_sampling_current.timestep(t)
|
|
x_noisy = self.model_sampling_current.calculate_input(t, x_noisy)
|
|
self.x_noisy_shape = x_noisy.shape
|
|
|
|
control = self.control_model(x=x_noisy.to(dtype), hint=self.cond_hint, timesteps=timestep.to(dtype), context=comfy.model_management.cast_to_device(context, x_noisy.device, dtype), **extra)
|
|
return self.control_merge(control, control_prev, output_dtype=None)
|
|
|
|
def pre_run_advanced(self, *args, **kwargs):
|
|
self.is_flux = "Flux" in str(type(self.control_model).__name__)
|
|
return super().pre_run_advanced(*args, **kwargs)
|
|
|
|
def apply_advanced_strengths_and_masks(self, x: Tensor, batched_number: int, flux_shape=None):
|
|
if self.is_flux:
|
|
flux_shape = self.x_noisy_shape
|
|
return super().apply_advanced_strengths_and_masks(x, batched_number, flux_shape)
|
|
|
|
def copy(self, subtype=None):
|
|
if subtype is None:
|
|
subtype = ControlNetAdvanced
|
|
c = subtype(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)
|
|
c.control_model = self.control_model
|
|
c.control_model_wrapped = self.control_model_wrapped
|
|
self.copy_to(c)
|
|
self.copy_to_advanced(c)
|
|
return c
|
|
|
|
def cleanup_advanced(self):
|
|
self.x_noisy_shape = None
|
|
return super().cleanup_advanced()
|
|
|
|
@staticmethod
|
|
def from_vanilla(v: ControlNet, timestep_keyframe: TimestepKeyframeGroup=None, subtype=None) -> 'ControlNetAdvanced':
|
|
if subtype is None:
|
|
subtype = ControlNetAdvanced
|
|
to_return = subtype(control_model=v.control_model, timestep_keyframes=timestep_keyframe,
|
|
global_average_pooling=v.global_average_pooling, compression_ratio=v.compression_ratio, latent_format=v.latent_format, load_device=v.load_device,
|
|
manual_cast_dtype=v.manual_cast_dtype, extra_conds=v.extra_conds, strength_type=v.strength_type, concat_mask=v.concat_mask, preprocess_image=v.preprocess_image)
|
|
v.copy_to(to_return)
|
|
to_return.control_model_wrapped = v.control_model_wrapped.clone() # needed to avoid breaking memory management system (parent tracking)
|
|
return to_return
|
|
|
|
|
|
class ControlNetSD35Advanced(ControlNetSD35, ControlNetAdvanced):
|
|
def __init__(self, *args, **kwargs):
|
|
ControlNetAdvanced.__init__(self, *args, **kwargs)
|
|
|
|
def copy(self):
|
|
return ControlNetAdvanced.copy(self, subtype=ControlNetSD35Advanced)
|
|
|
|
@staticmethod
|
|
def from_vanilla(v: ControlNetSD35, timestep_keyframe=None):
|
|
return ControlNetAdvanced.from_vanilla(v, timestep_keyframe, subtype=ControlNetSD35Advanced)
|
|
|
|
|
|
class T2IAdapterAdvanced(T2IAdapter, AdvancedControlBase):
|
|
def __init__(self, t2i_model, timestep_keyframes: TimestepKeyframeGroup, channels_in, compression_ratio=8, upscale_algorithm="nearest_exact", device=None):
|
|
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: dict[str, list[Tensor]], control_prev, output_dtype):
|
|
# match batch_size
|
|
# TODO: make this more efficient by modifying the cached self.control_input val instead of doing this every step
|
|
for key in control:
|
|
control_current = control[key]
|
|
for i in range(len(control_current)):
|
|
x = control_current[i]
|
|
if x is not None and x.size(0) == 1 and x.size(0) != self.batch_size:
|
|
control_current[i] = x.repeat(self.batch_size, 1, 1, 1)[:self.batch_size]
|
|
return AdvancedControlBase.control_merge_inject(self, control, control_prev, output_dtype)
|
|
|
|
def get_universal_weights(self) -> ControlWeights:
|
|
def t2i_weights_func(idx: int, control: dict[str, list[Tensor]], key: str):
|
|
if key == "middle":
|
|
return 1.0 * self.weights.extras.get(Extras.MIDDLE_MULT, 1.0)
|
|
c_len = 8 #len(control[key])
|
|
raw_weights = [(self.weights.base_multiplier ** float((c_len-1) - i)) for i in range(c_len)]
|
|
raw_weights = [raw_weights[-c_len], raw_weights[-3], raw_weights[-2], raw_weights[-1]]
|
|
raw_weights = get_properly_arranged_t2i_weights(raw_weights)
|
|
if key == "input":
|
|
raw_weights.reverse()
|
|
return raw_weights[idx]
|
|
return self.weights.copy_with_new_weights(new_weight_func=t2i_weights_func)
|
|
|
|
def get_calc_pow(self, idx: int, control: dict[str, list[Tensor]], key: str) -> int:
|
|
if key == "middle":
|
|
return 0
|
|
# match how T2IAdapterAdvanced deals with universal weights
|
|
c_len = 8 #len(control[key])
|
|
indeces = [(c_len-1) - i for i in range(c_len)]
|
|
indeces = [indeces[-c_len], indeces[-3], indeces[-2], indeces[-1]]
|
|
indeces = get_properly_arranged_t2i_weights(indeces)
|
|
if key == "input":
|
|
indeces.reverse() # need to reverse to match recent ComfyUI changes
|
|
return indeces[idx]
|
|
|
|
def get_control_advanced(self, x_noisy, t, cond, batched_number, transformer_options):
|
|
try:
|
|
# if sub indexes present, replace original hint with subsection
|
|
if self.sub_idxs is not None:
|
|
# cond hints
|
|
full_cond_hint_original = self.cond_hint_original
|
|
actual_cond_hint_orig = full_cond_hint_original
|
|
del self.cond_hint
|
|
self.cond_hint = None
|
|
if full_cond_hint_original.size(0) < self.full_latent_length:
|
|
actual_cond_hint_orig = extend_to_batch_size(tensor=full_cond_hint_original, batch_size=full_cond_hint_original.size(0))
|
|
self.cond_hint_original = actual_cond_hint_orig[self.sub_idxs]
|
|
# mask hints
|
|
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number)
|
|
return super().get_control(x_noisy, t, cond, batched_number, transformer_options)
|
|
finally:
|
|
if self.sub_idxs is not None:
|
|
# replace original cond hint
|
|
self.cond_hint_original = full_cond_hint_original
|
|
del full_cond_hint_original
|
|
|
|
def copy(self):
|
|
c = T2IAdapterAdvanced(self.t2i_model, self.timestep_keyframes, self.channels_in, self.compression_ratio, self.upscale_algorithm)
|
|
self.copy_to(c)
|
|
self.copy_to_advanced(c)
|
|
return c
|
|
|
|
def cleanup(self):
|
|
super().cleanup()
|
|
self.cleanup_advanced()
|
|
|
|
@staticmethod
|
|
def from_vanilla(v: T2IAdapter, timestep_keyframe: TimestepKeyframeGroup=None) -> 'T2IAdapterAdvanced':
|
|
to_return = T2IAdapterAdvanced(t2i_model=v.t2i_model, timestep_keyframes=timestep_keyframe, channels_in=v.channels_in,
|
|
compression_ratio=v.compression_ratio, upscale_algorithm=v.upscale_algorithm, device=v.device)
|
|
v.copy_to(to_return)
|
|
return to_return
|
|
|
|
|
|
class ControlLoraAdvanced(ControlLora, AdvancedControlBase):
|
|
def __init__(self, control_weights, timestep_keyframes: TimestepKeyframeGroup, global_average_pooling=False):
|
|
super().__init__(control_weights=control_weights, global_average_pooling=global_average_pooling)
|
|
AdvancedControlBase.__init__(self, super(), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.controllora())
|
|
# use some functions from ControlNetAdvanced
|
|
self.get_control_advanced = ControlNetAdvanced.get_control_advanced.__get__(self, type(self))
|
|
self.sliding_get_control = ControlNetAdvanced.sliding_get_control.__get__(self, type(self))
|
|
|
|
def get_universal_weights(self) -> ControlWeights:
|
|
raw_weights = [(self.weights.base_multiplier ** float(9 - i)) for i in range(10)]
|
|
return self.weights.copy_with_new_weights(raw_weights)
|
|
|
|
def copy(self):
|
|
c = ControlLoraAdvanced(self.control_weights, self.timestep_keyframes, global_average_pooling=self.global_average_pooling)
|
|
self.copy_to(c)
|
|
self.copy_to_advanced(c)
|
|
return c
|
|
|
|
def cleanup(self):
|
|
super().cleanup()
|
|
self.cleanup_advanced()
|
|
|
|
@staticmethod
|
|
def from_vanilla(v: ControlLora, timestep_keyframe: TimestepKeyframeGroup=None) -> 'ControlLoraAdvanced':
|
|
to_return = ControlLoraAdvanced(control_weights=v.control_weights, timestep_keyframes=timestep_keyframe,
|
|
global_average_pooling=v.global_average_pooling)
|
|
v.copy_to(to_return)
|
|
return to_return
|
|
|
|
|
|
class SVDControlNetAdvanced(ControlNetAdvanced):
|
|
def __init__(self, control_model: SVDControlNet, timestep_keyframes: TimestepKeyframeGroup, global_average_pooling=False, load_device=None, manual_cast_dtype=None):
|
|
super().__init__(control_model=control_model, timestep_keyframes=timestep_keyframes, global_average_pooling=global_average_pooling, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
|
|
|
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
|
|
|
|
def get_control_advanced(self, x_noisy, t, cond, batched_number, transformer_options):
|
|
control_prev = None
|
|
if self.previous_controlnet is not None:
|
|
control_prev = self.previous_controlnet.get_control(x_noisy, t, cond, batched_number, transformer_options)
|
|
|
|
if self.timestep_range is not None:
|
|
if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]:
|
|
if control_prev is not None:
|
|
return control_prev
|
|
else:
|
|
return None
|
|
|
|
dtype = self.control_model.dtype
|
|
if self.manual_cast_dtype is not None:
|
|
dtype = self.manual_cast_dtype
|
|
|
|
output_dtype = x_noisy.dtype
|
|
# make cond_hint appropriate dimensions
|
|
# TODO: change this to not require cond_hint upscaling every step when self.sub_idxs are present
|
|
if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[2] * 8 != self.cond_hint.shape[2] or x_noisy.shape[3] * 8 != self.cond_hint.shape[3]:
|
|
if self.cond_hint is not None:
|
|
del self.cond_hint
|
|
self.cond_hint = None
|
|
# if self.cond_hint_original length greater or equal to real latent count, subdivide it before scaling
|
|
if self.sub_idxs is not None:
|
|
actual_cond_hint_orig = self.cond_hint_original
|
|
if self.cond_hint_original.size(0) < self.full_latent_length:
|
|
actual_cond_hint_orig = extend_to_batch_size(tensor=actual_cond_hint_orig, batch_size=self.full_latent_length)
|
|
self.cond_hint = comfy.utils.common_upscale(actual_cond_hint_orig[self.sub_idxs], x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(x_noisy.device)
|
|
else:
|
|
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original, x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(x_noisy.device)
|
|
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)
|
|
|
|
# prepare mask_cond_hint
|
|
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number, dtype=dtype)
|
|
|
|
context = cond.get('crossattn_controlnet', cond['c_crossattn'])
|
|
# uses 'y' in new ComfyUI update
|
|
y = cond.get('y', None)
|
|
if y is not None:
|
|
y = comfy.model_base.convert_tensor(y, dtype, x_noisy.device)
|
|
timestep = self.model_sampling_current.timestep(t)
|
|
x_noisy = self.model_sampling_current.calculate_input(t, x_noisy)
|
|
# concat c_concat if exists (should exist for SVD), doubling channels to 8
|
|
if cond.get('c_concat', None) is not None:
|
|
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=comfy.model_management.cast_to_device(context, x_noisy.device, dtype), y=y, cond=cond)
|
|
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)
|
|
self.copy_to(c)
|
|
self.copy_to_advanced(c)
|
|
return c
|
|
|
|
|
|
class SparseCtrlAdvanced(ControlNetAdvanced):
|
|
def __init__(self, control_model: SparseControlNet, motion_model: InterfaceAnimateDiffModel,
|
|
timestep_keyframes: TimestepKeyframeGroup, sparse_settings: SparseSettings=None, global_average_pooling=False, load_device=None, manual_cast_dtype=None):
|
|
super().__init__(control_model=None, timestep_keyframes=timestep_keyframes, global_average_pooling=global_average_pooling, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
|
self.control_model = control_model
|
|
if control_model is not None:
|
|
self.control_model_wrapped: ModelPatcher = create_sparse_modelpatcher(self.control_model, motion_model, load_device=load_device, offload_device=comfy.model_management.unet_offload_device())
|
|
self.prepare_conditioning_info()
|
|
self.add_compatible_weight(ControlWeightType.SPARSECTRL)
|
|
self.postpone_condhint_latents_check = 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
|
|
|
|
def prepare_conditioning_info(self):
|
|
if self.control_model.use_simplified_conditioning_embedding:
|
|
# TODO: allow vae_optional to be used instead of preprocessor
|
|
#self.require_vae = True
|
|
self.allow_condhint_latents = True
|
|
|
|
@property
|
|
def motion_model(self) -> InterfaceAnimateDiffModel:
|
|
motion_models = self.control_model_wrapped.get_additional_models_with_key(WrapperConsts.ACN)
|
|
if len(motion_models) == 0:
|
|
return None
|
|
return motion_models[0].model
|
|
|
|
def get_control_advanced(self, x_noisy: Tensor, t, cond, batched_number: int, transformer_options):
|
|
# normal ControlNet stuff
|
|
control_prev = None
|
|
if self.previous_controlnet is not None:
|
|
control_prev = self.previous_controlnet.get_control(x_noisy, t, cond, batched_number, transformer_options)
|
|
|
|
if self.timestep_range is not None:
|
|
if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]:
|
|
if control_prev is not None:
|
|
return control_prev
|
|
else:
|
|
return None
|
|
|
|
dtype = self.control_model.dtype
|
|
if self.manual_cast_dtype is not None:
|
|
dtype = self.manual_cast_dtype
|
|
output_dtype = x_noisy.dtype
|
|
# set actual input length on motion model
|
|
actual_length = x_noisy.size(0)//batched_number
|
|
full_length = actual_length if self.sub_idxs is None else self.full_latent_length
|
|
if self.motion_model is not None:
|
|
self.motion_model.set_video_length(video_length=actual_length, full_length=full_length)
|
|
# prepare cond_hint, if needed
|
|
dim_mult = 1 if self.control_model.use_simplified_conditioning_embedding else 8
|
|
if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[2]*dim_mult != self.cond_hint.shape[2] or x_noisy.shape[3]*dim_mult != self.cond_hint.shape[3]:
|
|
# clear out cond_hint and conditioning_mask
|
|
if self.cond_hint is not None:
|
|
del self.cond_hint
|
|
self.cond_hint = None
|
|
# first, figure out which cond idxs are relevant, and where they fit in
|
|
cond_idxs, hint_order = self.sparse_settings.sparse_method.get_indexes(hint_length=self.cond_hint_original.size(0), full_length=full_length,
|
|
sub_idxs=self.sub_idxs if self.sparse_settings.is_context_aware() else None)
|
|
range_idxs = list(range(full_length)) if self.sub_idxs is None else self.sub_idxs
|
|
hint_idxs = [] # idxs in cond_idxs
|
|
local_idxs = [] # idx to put in final cond_hint
|
|
for i,cond_idx in enumerate(cond_idxs):
|
|
if cond_idx in range_idxs:
|
|
hint_idxs.append(i)
|
|
local_idxs.append(range_idxs.index(cond_idx))
|
|
# log_string = f"cond_idxs: {cond_idxs}, local_idxs: {local_idxs}, hint_idxs: {hint_idxs}, hint_order: {hint_order}"
|
|
# if self.sub_idxs is not None:
|
|
# log_string += f" sub_idxs: {self.sub_idxs[0]}-{self.sub_idxs[-1]}"
|
|
# logger.warn(log_string)
|
|
# determine cond/uncond indexes that will get masked
|
|
self.local_sparse_idxs = []
|
|
self.local_sparse_idxs_inverse = list(range(x_noisy.size(0)))
|
|
for batch_idx in range(batched_number):
|
|
for i in local_idxs:
|
|
actual_i = i+(batch_idx*actual_length)
|
|
self.local_sparse_idxs.append(actual_i)
|
|
if actual_i in self.local_sparse_idxs_inverse:
|
|
self.local_sparse_idxs_inverse.remove(actual_i)
|
|
# sub_cond_hint now contains the hints relevant to current x_noisy
|
|
if hint_order is None:
|
|
sub_cond_hint = self.cond_hint_original[hint_idxs].to(dtype).to(x_noisy.device)
|
|
else:
|
|
sub_cond_hint = self.cond_hint_original[hint_order][hint_idxs].to(dtype).to(x_noisy.device)
|
|
# scale cond_hints to match noisy input
|
|
if self.control_model.use_simplified_conditioning_embedding:
|
|
# RGB SparseCtrl; the inputs are latents - use bilinear to avoid blocky artifacts
|
|
sub_cond_hint = self.model_latent_format.process_in(sub_cond_hint) # multiplies by model scale factor
|
|
sub_cond_hint = comfy.utils.common_upscale(sub_cond_hint, x_noisy.shape[3], x_noisy.shape[2], "nearest-exact", "center").to(dtype).to(x_noisy.device)
|
|
else:
|
|
# other SparseCtrl; inputs are typical images
|
|
sub_cond_hint = comfy.utils.common_upscale(sub_cond_hint, x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(x_noisy.device)
|
|
# prepare cond_hint (b, c, h ,w)
|
|
cond_shape = list(sub_cond_hint.shape)
|
|
cond_shape[0] = len(range_idxs)
|
|
self.cond_hint = torch.zeros(cond_shape).to(dtype).to(x_noisy.device)
|
|
self.cond_hint[local_idxs] = sub_cond_hint[:]
|
|
# prepare cond_mask (b, 1, h, w)
|
|
cond_shape[1] = 1
|
|
cond_mask = torch.zeros(cond_shape).to(dtype).to(x_noisy.device)
|
|
cond_mask[local_idxs] = self.sparse_settings.sparse_mask_mult * self.weights.extras.get(SparseConst.MASK_MULT, 1.0)
|
|
# combine cond_hint and cond_mask into (b, c+1, h, w)
|
|
if not self.sparse_settings.merged:
|
|
self.cond_hint = torch.cat([self.cond_hint, cond_mask], dim=1)
|
|
del sub_cond_hint
|
|
del cond_mask
|
|
# make cond_hint match x_noisy batch
|
|
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)
|
|
|
|
# prepare mask_cond_hint
|
|
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number, dtype=dtype)
|
|
|
|
context = cond['c_crossattn']
|
|
y = cond.get('y', None)
|
|
if y is not None:
|
|
y = comfy.model_base.convert_tensor(y, dtype, x_noisy.device)
|
|
timestep = self.model_sampling_current.timestep(t)
|
|
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=comfy.model_management.cast_to_device(context, x_noisy.device, dtype), y=y)
|
|
return self.control_merge(control, control_prev, output_dtype)
|
|
|
|
def apply_advanced_strengths_and_masks(self, x: Tensor, batched_number: int, *args, **kwargs):
|
|
# apply mults to indexes with and without a direct condhint
|
|
x[self.local_sparse_idxs] *= self.sparse_settings.sparse_hint_mult * self.weights.extras.get(SparseConst.HINT_MULT, 1.0)
|
|
x[self.local_sparse_idxs_inverse] *= self.sparse_settings.sparse_nonhint_mult * self.weights.extras.get(SparseConst.NONHINT_MULT, 1.0)
|
|
return super().apply_advanced_strengths_and_masks(x, batched_number, *args, **kwargs)
|
|
|
|
def pre_run_advanced(self, model, percent_to_timestep_function):
|
|
super().pre_run_advanced(model, percent_to_timestep_function)
|
|
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
|
|
self.model_latent_format = model.latent_format # LatentFormat object, used to process_in latent cond hint
|
|
if self.motion_model is not None:
|
|
self.motion_model.cleanup()
|
|
self.motion_model.set_effect(self.sparse_settings.motion_strength)
|
|
self.motion_model.set_scale(self.sparse_settings.motion_scale)
|
|
|
|
def cleanup_advanced(self):
|
|
super().cleanup_advanced()
|
|
if self.model_latent_format is not None:
|
|
del self.model_latent_format
|
|
self.model_latent_format = None
|
|
self.local_sparse_idxs = None
|
|
self.local_sparse_idxs_inverse = None
|
|
if self.motion_model is not None:
|
|
self.motion_model.cleanup()
|
|
|
|
def copy(self):
|
|
c = SparseCtrlAdvanced(None, None, self.timestep_keyframes, self.sparse_settings, self.global_average_pooling, self.load_device, self.manual_cast_dtype)
|
|
c.control_model = self.control_model
|
|
c.control_model_wrapped = self.control_model_wrapped
|
|
self.prepare_conditioning_info()
|
|
self.copy_to(c)
|
|
self.copy_to_advanced(c)
|
|
return c
|
|
|
|
def get_models(self):
|
|
to_return = super().get_models()
|
|
to_return.extend(self.control_model_wrapped.get_additional_models())
|
|
return to_return
|
|
|
|
|
|
def load_controlnet(ckpt_path, timestep_keyframe: TimestepKeyframeGroup=None, model=None):
|
|
controlnet_data = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
|
|
# from pathlib import Path
|
|
# log_name = ckpt_path.split('\\')[-1]
|
|
# with open(Path(__file__).parent.parent.parent / rf"keys_{log_name}.txt", "w") as afile:
|
|
# for key, value in controlnet_data.items():
|
|
# afile.write(f"{key}:\t{value.shape}\n")
|
|
control = None
|
|
# check if a non-vanilla ControlNet
|
|
controlnet_type = ControlWeightType.DEFAULT
|
|
has_controlnet_key = False
|
|
has_motion_modules_key = False
|
|
has_temporal_res_block_key = False
|
|
for key in controlnet_data:
|
|
# LLLite check
|
|
if "lllite" in key:
|
|
controlnet_type = ControlWeightType.CONTROLLLLITE
|
|
break
|
|
# SparseCtrl check
|
|
elif "motion_modules" in key:
|
|
has_motion_modules_key = True
|
|
elif "controlnet" in key:
|
|
has_controlnet_key = True
|
|
# SVD-ControlNet check
|
|
elif "temporal_res_block" in key:
|
|
has_temporal_res_block_key = True
|
|
# ControlNet++ check
|
|
elif "task_embedding" in key:
|
|
pass
|
|
# CtrLoRA check
|
|
elif "lora_layer" in key:
|
|
controlnet_type = ControlWeightType.CTRLORA
|
|
break
|
|
|
|
if has_controlnet_key and has_motion_modules_key:
|
|
controlnet_type = ControlWeightType.SPARSECTRL
|
|
elif has_controlnet_key and has_temporal_res_block_key:
|
|
controlnet_type = ControlWeightType.SVD_CONTROLNET
|
|
|
|
if controlnet_type != ControlWeightType.DEFAULT:
|
|
if controlnet_type == ControlWeightType.CONTROLLLLITE:
|
|
control = load_controllllite(ckpt_path, controlnet_data=controlnet_data, timestep_keyframe=timestep_keyframe)
|
|
elif controlnet_type == ControlWeightType.SPARSECTRL:
|
|
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)
|
|
elif controlnet_type == ControlWeightType.CTRLORA:
|
|
raise Exception("This is a CtrLoRA; use the Load CtrLoRA Model node.")
|
|
# otherwise, load vanilla ControlNet
|
|
else:
|
|
try:
|
|
# hacky way of getting load_torch_file in load_controlnet to use already-present controlnet_data and not redo loading
|
|
orig_load_torch_file = comfy.utils.load_torch_file
|
|
comfy.utils.load_torch_file = load_torch_file_with_dict_factory(controlnet_data, orig_load_torch_file)
|
|
control = comfy_cn.load_controlnet(ckpt_path, model=model)
|
|
finally:
|
|
comfy.utils.load_torch_file = orig_load_torch_file
|
|
if control is None:
|
|
raise Exception(f"Something went wrong when loading '{ckpt_path}'; ControlNet is None.")
|
|
return convert_to_advanced(control, timestep_keyframe=timestep_keyframe)
|
|
|
|
|
|
def convert_to_advanced(control, timestep_keyframe: TimestepKeyframeGroup=None):
|
|
# if already advanced, leave it be
|
|
if is_advanced_controlnet(control):
|
|
return control
|
|
# if exactly ControlNet returned, transform it into ControlNetAdvanced
|
|
if type(control) == ControlNet:
|
|
control = ControlNetAdvanced.from_vanilla(v=control, timestep_keyframe=timestep_keyframe)
|
|
if is_sd3_advanced_controlnet(control):
|
|
control.require_vae = True
|
|
return control
|
|
# if exactly ControlNetSD35 returned, transform into ControlNetSD35Advanced
|
|
elif type(control) == ControlNetSD35:
|
|
control = ControlNetSD35Advanced.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)
|
|
# if T2IAdapter returned, transform it into T2IAdapterAdvanced
|
|
elif isinstance(control, T2IAdapter):
|
|
return T2IAdapterAdvanced.from_vanilla(v=control, timestep_keyframe=timestep_keyframe)
|
|
# otherwise, leave it be - might be something I am not supporting yet
|
|
return control
|
|
|
|
|
|
def convert_all_to_advanced(conds: dict[str, list[dict[str]]]) -> tuple[bool, list]:
|
|
cache = {}
|
|
modified = False
|
|
new_conds = {}
|
|
for cond_type in conds:
|
|
converted_cond: list[dict[str]] = None
|
|
cond = conds[cond_type]
|
|
if cond is not None:
|
|
for actual_cond in cond:
|
|
need_to_convert = False
|
|
if "control" in actual_cond:
|
|
if not are_all_advanced_controlnet(actual_cond["control"]):
|
|
need_to_convert = True
|
|
break
|
|
if not need_to_convert:
|
|
converted_cond = cond
|
|
else:
|
|
converted_cond = []
|
|
for actual_cond in cond:
|
|
if not isinstance(actual_cond, dict):
|
|
converted_cond.append(actual_cond)
|
|
continue
|
|
if "control" not in actual_cond:
|
|
converted_cond.append(actual_cond)
|
|
elif are_all_advanced_controlnet(actual_cond["control"]):
|
|
converted_cond.append(actual_cond)
|
|
else:
|
|
actual_cond = actual_cond.copy()
|
|
actual_cond["control"] = _convert_all_control_to_advanced(actual_cond["control"], cache)
|
|
converted_cond.append(actual_cond)
|
|
modified = True
|
|
new_conds[cond_type] = converted_cond
|
|
return modified, new_conds
|
|
|
|
|
|
def _convert_all_control_to_advanced(input_object: ControlBase, cache: dict):
|
|
output_object = input_object
|
|
# iteratively convert to advanced, if needed
|
|
next_cn = None
|
|
curr_cn = input_object
|
|
iter = 0
|
|
while curr_cn is not None:
|
|
if not is_advanced_controlnet(curr_cn):
|
|
# if already in cache, then conversion was done before, so just link it and exit
|
|
if curr_cn in cache:
|
|
new_cn = cache[curr_cn]
|
|
if next_cn is not None:
|
|
setattr(next_cn, ORIG_PREVIOUS_CONTROLNET, next_cn.previous_controlnet)
|
|
next_cn.previous_controlnet = new_cn
|
|
if iter == 0: # if was top-level controlnet, that's the new output
|
|
output_object = new_cn
|
|
break
|
|
try:
|
|
# convert to advanced, and assign previous_controlnet (convert doesn't transfer it)
|
|
new_cn = convert_to_advanced(curr_cn)
|
|
except Exception as e:
|
|
raise Exception("Failed to automatically convert a ControlNet to Advanced to support sliding window context.", e)
|
|
new_cn.previous_controlnet = curr_cn.previous_controlnet
|
|
if iter == 0: # if was top-level controlnet, that's the new output
|
|
output_object = new_cn
|
|
# if next_cn is present, then it needs to be pointed to new_cn
|
|
if next_cn is not None:
|
|
setattr(next_cn, ORIG_PREVIOUS_CONTROLNET, next_cn.previous_controlnet)
|
|
next_cn.previous_controlnet = new_cn
|
|
# add to cache
|
|
cache[curr_cn] = new_cn
|
|
curr_cn = new_cn
|
|
next_cn = curr_cn
|
|
curr_cn = curr_cn.previous_controlnet
|
|
iter += 1
|
|
return output_object
|
|
|
|
|
|
def restore_all_controlnet_conns(conds: dict[str, list[dict[str]]]):
|
|
# if a cn has an _orig_previous_controlnet property, restore it and delete
|
|
for cond_type in conds:
|
|
cond = conds[cond_type]
|
|
if cond is not None:
|
|
for actual_cond in cond:
|
|
if "control" in actual_cond:
|
|
# if ACN is the one to have initialized it, delete it
|
|
# TODO: maybe check if someone else did a similar hack, and carefully pluck out our stuff?
|
|
if CONTROL_INIT_BY_ACN in actual_cond:
|
|
actual_cond.pop("control")
|
|
actual_cond.pop(CONTROL_INIT_BY_ACN)
|
|
else:
|
|
_restore_all_controlnet_conns(actual_cond["control"])
|
|
|
|
|
|
|
|
def _restore_all_controlnet_conns(input_object: ControlBase):
|
|
# restore original previous_controlnet if needed
|
|
curr_cn = input_object
|
|
while curr_cn is not None:
|
|
if hasattr(curr_cn, ORIG_PREVIOUS_CONTROLNET):
|
|
curr_cn.previous_controlnet = getattr(curr_cn, ORIG_PREVIOUS_CONTROLNET)
|
|
delattr(curr_cn, ORIG_PREVIOUS_CONTROLNET)
|
|
curr_cn = curr_cn.previous_controlnet
|
|
|
|
|
|
def are_all_advanced_controlnet(input_object: ControlBase):
|
|
# iteratively check if linked controlnets objects are all advanced
|
|
curr_cn = input_object
|
|
while curr_cn is not None:
|
|
if not is_advanced_controlnet(curr_cn):
|
|
return False
|
|
curr_cn = curr_cn.previous_controlnet
|
|
return True
|
|
|
|
|
|
def is_advanced_controlnet(input_object):
|
|
return hasattr(input_object, "sub_idxs")
|
|
|
|
|
|
def is_sd3_advanced_controlnet(input_object: ControlNetAdvanced):
|
|
return type(input_object) in [ControlNetAdvanced, ControlNetSD35Advanced] 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)
|
|
# first, separate out motion part from normal controlnet part and attempt to load that portion
|
|
motion_data = {}
|
|
for key in list(controlnet_data.keys()):
|
|
if "temporal" in key:
|
|
motion_data[key] = controlnet_data.pop(key)
|
|
if len(motion_data) == 0:
|
|
raise ValueError(f"No motion-related keys in '{ckpt_path}'; not a valid SparseCtrl model!")
|
|
|
|
# now, load as if it was a normal controlnet - mostly copied from comfy load_controlnet function
|
|
controlnet_config: dict[str] = None
|
|
is_diffusers = False
|
|
use_simplified_conditioning_embedding = False
|
|
if "controlnet_cond_embedding.conv_in.weight" in controlnet_data:
|
|
is_diffusers = True
|
|
if "controlnet_cond_embedding.weight" in controlnet_data:
|
|
is_diffusers = True
|
|
use_simplified_conditioning_embedding = True
|
|
if is_diffusers: #diffusers format
|
|
unet_dtype = comfy.model_management.unet_dtype()
|
|
controlnet_config = comfy.model_detection.unet_config_from_diffusers_unet(controlnet_data, unet_dtype)
|
|
diffusers_keys = comfy.utils.unet_to_diffusers(controlnet_config)
|
|
diffusers_keys["controlnet_mid_block.weight"] = "middle_block_out.0.weight"
|
|
diffusers_keys["controlnet_mid_block.bias"] = "middle_block_out.0.bias"
|
|
|
|
count = 0
|
|
loop = True
|
|
while loop:
|
|
suffix = [".weight", ".bias"]
|
|
for s in suffix:
|
|
k_in = "controlnet_down_blocks.{}{}".format(count, s)
|
|
k_out = "zero_convs.{}.0{}".format(count, s)
|
|
if k_in not in controlnet_data:
|
|
loop = False
|
|
break
|
|
diffusers_keys[k_in] = k_out
|
|
count += 1
|
|
# normal conditioning embedding
|
|
if not use_simplified_conditioning_embedding:
|
|
count = 0
|
|
loop = True
|
|
while loop:
|
|
suffix = [".weight", ".bias"]
|
|
for s in suffix:
|
|
if count == 0:
|
|
k_in = "controlnet_cond_embedding.conv_in{}".format(s)
|
|
else:
|
|
k_in = "controlnet_cond_embedding.blocks.{}{}".format(count - 1, s)
|
|
k_out = "input_hint_block.{}{}".format(count * 2, s)
|
|
if k_in not in controlnet_data:
|
|
k_in = "controlnet_cond_embedding.conv_out{}".format(s)
|
|
loop = False
|
|
diffusers_keys[k_in] = k_out
|
|
count += 1
|
|
# simplified conditioning embedding
|
|
else:
|
|
count = 0
|
|
suffix = [".weight", ".bias"]
|
|
for s in suffix:
|
|
k_in = "controlnet_cond_embedding{}".format(s)
|
|
k_out = "input_hint_block.{}{}".format(count, s)
|
|
diffusers_keys[k_in] = k_out
|
|
|
|
new_sd = {}
|
|
for k in diffusers_keys:
|
|
if k in controlnet_data:
|
|
new_sd[diffusers_keys[k]] = controlnet_data.pop(k)
|
|
|
|
leftover_keys = controlnet_data.keys()
|
|
if len(leftover_keys) > 0:
|
|
logger.info("leftover keys:", leftover_keys)
|
|
controlnet_data = new_sd
|
|
|
|
pth_key = 'control_model.zero_convs.0.0.weight'
|
|
pth = False
|
|
key = 'zero_convs.0.0.weight'
|
|
if pth_key in controlnet_data:
|
|
pth = True
|
|
key = pth_key
|
|
prefix = "control_model."
|
|
elif key in controlnet_data:
|
|
prefix = ""
|
|
else:
|
|
raise ValueError("The provided model is not a valid SparseCtrl model! [ErrorCode: HORSERADISH]")
|
|
|
|
if controlnet_config is None:
|
|
unet_dtype = comfy.model_management.unet_dtype()
|
|
controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, unet_dtype, True).unet_config
|
|
load_device = comfy.model_management.get_torch_device()
|
|
manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device)
|
|
if manual_cast_dtype is not None:
|
|
controlnet_config["operations"] = manual_cast_clean_groupnorm
|
|
else:
|
|
controlnet_config["operations"] = disable_weight_init_clean_groupnorm
|
|
controlnet_config["dtype"] = unet_dtype
|
|
controlnet_config.pop("out_channels")
|
|
# get proper hint channels
|
|
if use_simplified_conditioning_embedding:
|
|
controlnet_config["hint_channels"] = controlnet_data["{}input_hint_block.0.weight".format(prefix)].shape[1]
|
|
controlnet_config["use_simplified_conditioning_embedding"] = use_simplified_conditioning_embedding
|
|
else:
|
|
controlnet_config["hint_channels"] = controlnet_data["{}input_hint_block.0.weight".format(prefix)].shape[1]
|
|
controlnet_config["use_simplified_conditioning_embedding"] = use_simplified_conditioning_embedding
|
|
control_model = SparseControlNet(**controlnet_config)
|
|
|
|
if pth:
|
|
if 'difference' in controlnet_data:
|
|
if model is not None:
|
|
comfy.model_management.load_models_gpu([model])
|
|
model_sd = model.model_state_dict()
|
|
for x in controlnet_data:
|
|
c_m = "control_model."
|
|
if x.startswith(c_m):
|
|
sd_key = "diffusion_model.{}".format(x[len(c_m):])
|
|
if sd_key in model_sd:
|
|
cd = controlnet_data[x]
|
|
cd += model_sd[sd_key].type(cd.dtype).to(cd.device)
|
|
else:
|
|
logger.warning("WARNING: Loaded a diff SparseCtrl without a model. It will very likely not work.")
|
|
|
|
class WeightsLoader(torch.nn.Module):
|
|
pass
|
|
w = WeightsLoader()
|
|
w.control_model = control_model
|
|
missing, unexpected = w.load_state_dict(controlnet_data, strict=False)
|
|
else:
|
|
missing, unexpected = control_model.load_state_dict(controlnet_data, strict=False)
|
|
if len(missing) > 0 or len(unexpected) > 0:
|
|
logger.info(f"SparseCtrl ControlNet: {missing}, {unexpected}")
|
|
# cast control_model to the intended dtype; load_state_dict can leave weights
|
|
# in their on-disk dtype (e.g. comfy's lazy/zero-copy state dict loading), which
|
|
# would otherwise mismatch the activations at runtime
|
|
control_model = control_model.to(unet_dtype)
|
|
|
|
global_average_pooling = False
|
|
filename = os.path.splitext(ckpt_path)[0]
|
|
if filename.endswith("_shuffle") or filename.endswith("_shuffle_fp16"): #TODO: smarter way of enabling global_average_pooling
|
|
global_average_pooling = True
|
|
|
|
# actually load motion portion of model now
|
|
motion_model = load_sparsectrl_motionmodel(ckpt_path=ckpt_path, motion_data=motion_data, ops=controlnet_config.get("operations", None)).to(comfy.model_management.unet_dtype())
|
|
# both motion portion and controlnet portions are loaded; ignore motion_model if shouldn't use motion portion
|
|
if not sparse_settings.use_motion:
|
|
motion_model = None
|
|
|
|
control = SparseCtrlAdvanced(control_model, motion_model, timestep_keyframes=timestep_keyframe, sparse_settings=sparse_settings, global_average_pooling=global_average_pooling, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
|
return control
|
|
|
|
|
|
def load_svdcontrolnet(ckpt_path: str, controlnet_data: dict[str, Tensor]=None, timestep_keyframe: TimestepKeyframeGroup=None, model=None):
|
|
if controlnet_data is None:
|
|
controlnet_data = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
|
|
|
|
controlnet_config = None
|
|
if "controlnet_cond_embedding.conv_in.weight" in controlnet_data: #diffusers format
|
|
unet_dtype = comfy.model_management.unet_dtype()
|
|
controlnet_config = svd_unet_config_from_diffusers_unet(controlnet_data, unet_dtype)
|
|
diffusers_keys = svd_unet_to_diffusers(controlnet_config)
|
|
diffusers_keys["controlnet_mid_block.weight"] = "middle_block_out.0.weight"
|
|
diffusers_keys["controlnet_mid_block.bias"] = "middle_block_out.0.bias"
|
|
|
|
count = 0
|
|
loop = True
|
|
while loop:
|
|
suffix = [".weight", ".bias"]
|
|
for s in suffix:
|
|
k_in = "controlnet_down_blocks.{}{}".format(count, s)
|
|
k_out = "zero_convs.{}.0{}".format(count, s)
|
|
if k_in not in controlnet_data:
|
|
loop = False
|
|
break
|
|
diffusers_keys[k_in] = k_out
|
|
count += 1
|
|
|
|
count = 0
|
|
loop = True
|
|
while loop:
|
|
suffix = [".weight", ".bias"]
|
|
for s in suffix:
|
|
if count == 0:
|
|
k_in = "controlnet_cond_embedding.conv_in{}".format(s)
|
|
else:
|
|
k_in = "controlnet_cond_embedding.blocks.{}{}".format(count - 1, s)
|
|
k_out = "input_hint_block.{}{}".format(count * 2, s)
|
|
if k_in not in controlnet_data:
|
|
k_in = "controlnet_cond_embedding.conv_out{}".format(s)
|
|
loop = False
|
|
diffusers_keys[k_in] = k_out
|
|
count += 1
|
|
|
|
new_sd = {}
|
|
for k in diffusers_keys:
|
|
if k in controlnet_data:
|
|
new_sd[diffusers_keys[k]] = controlnet_data.pop(k)
|
|
|
|
leftover_keys = controlnet_data.keys()
|
|
if len(leftover_keys) > 0:
|
|
spatial_leftover_keys = []
|
|
temporal_leftover_keys = []
|
|
other_leftover_keys = []
|
|
for key in leftover_keys:
|
|
if "spatial" in key:
|
|
spatial_leftover_keys.append(key)
|
|
elif "temporal" in key:
|
|
temporal_leftover_keys.append(key)
|
|
else:
|
|
other_leftover_keys.append(key)
|
|
logger.warn(f"spatial_leftover_keys ({len(spatial_leftover_keys)}): {spatial_leftover_keys}")
|
|
logger.warn(f"temporal_leftover_keys ({len(temporal_leftover_keys)}): {temporal_leftover_keys}")
|
|
logger.warn(f"other_leftover_keys ({len(other_leftover_keys)}): {other_leftover_keys}")
|
|
#print("leftover keys:", leftover_keys)
|
|
controlnet_data = new_sd
|
|
|
|
pth_key = 'control_model.zero_convs.0.0.weight'
|
|
pth = False
|
|
key = 'zero_convs.0.0.weight'
|
|
if pth_key in controlnet_data:
|
|
pth = True
|
|
key = pth_key
|
|
prefix = "control_model."
|
|
elif key in controlnet_data:
|
|
prefix = ""
|
|
else:
|
|
raise ValueError("The provided model is not a valid SVD-ControlNet model! [ErrorCode: MUSTARD]")
|
|
|
|
if controlnet_config is None:
|
|
unet_dtype = comfy.model_management.unet_dtype()
|
|
controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, unet_dtype, True).unet_config
|
|
load_device = comfy.model_management.get_torch_device()
|
|
manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device)
|
|
if manual_cast_dtype is not None:
|
|
controlnet_config["operations"] = comfy.ops.manual_cast
|
|
controlnet_config["dtype"] = unet_dtype
|
|
controlnet_config.pop("out_channels")
|
|
controlnet_config["hint_channels"] = controlnet_data["{}input_hint_block.0.weight".format(prefix)].shape[1]
|
|
control_model = SVDControlNet(**controlnet_config)
|
|
|
|
if pth:
|
|
if 'difference' in controlnet_data:
|
|
if model is not None:
|
|
comfy.model_management.load_models_gpu([model])
|
|
model_sd = model.model_state_dict()
|
|
for x in controlnet_data:
|
|
c_m = "control_model."
|
|
if x.startswith(c_m):
|
|
sd_key = "diffusion_model.{}".format(x[len(c_m):])
|
|
if sd_key in model_sd:
|
|
cd = controlnet_data[x]
|
|
cd += model_sd[sd_key].type(cd.dtype).to(cd.device)
|
|
else:
|
|
print("WARNING: Loaded a diff controlnet without a model. It will very likely not work.")
|
|
|
|
class WeightsLoader(torch.nn.Module):
|
|
pass
|
|
w = WeightsLoader()
|
|
w.control_model = control_model
|
|
missing, unexpected = w.load_state_dict(controlnet_data, strict=False)
|
|
else:
|
|
missing, unexpected = control_model.load_state_dict(controlnet_data, strict=False)
|
|
if len(missing) > 0 or len(unexpected) > 0:
|
|
logger.info(f"SVD-ControlNet: {missing}, {unexpected}")
|
|
# cast control_model to the intended dtype; load_state_dict can leave weights
|
|
# in their on-disk dtype (e.g. comfy's lazy/zero-copy state dict loading), which
|
|
# would otherwise mismatch the activations at runtime
|
|
control_model = control_model.to(unet_dtype)
|
|
|
|
global_average_pooling = False
|
|
filename = os.path.splitext(ckpt_path)[0]
|
|
if filename.endswith("_shuffle") or filename.endswith("_shuffle_fp16"): #TODO: smarter way of enabling global_average_pooling
|
|
global_average_pooling = True
|
|
|
|
control = SVDControlNetAdvanced(control_model, timestep_keyframes=timestep_keyframe, global_average_pooling=global_average_pooling, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
|
return control
|
|
|