mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-20 07:22:34 +08:00
Merge upstream/master, keep local README.md
This commit is contained in:
commit
28992b85c2
@ -796,6 +796,8 @@ def archive_model_dtypes(model):
|
|||||||
for name, module in model.named_modules():
|
for name, module in model.named_modules():
|
||||||
for param_name, param in module.named_parameters(recurse=False):
|
for param_name, param in module.named_parameters(recurse=False):
|
||||||
setattr(module, f"{param_name}_comfy_model_dtype", param.dtype)
|
setattr(module, f"{param_name}_comfy_model_dtype", param.dtype)
|
||||||
|
for buf_name, buf in module.named_buffers(recurse=False):
|
||||||
|
setattr(module, f"{buf_name}_comfy_model_dtype", buf.dtype)
|
||||||
|
|
||||||
|
|
||||||
def cleanup_models():
|
def cleanup_models():
|
||||||
@ -828,11 +830,14 @@ def unet_offload_device():
|
|||||||
return torch.device("cpu")
|
return torch.device("cpu")
|
||||||
|
|
||||||
def unet_inital_load_device(parameters, dtype):
|
def unet_inital_load_device(parameters, dtype):
|
||||||
|
cpu_dev = torch.device("cpu")
|
||||||
|
if comfy.memory_management.aimdo_enabled:
|
||||||
|
return cpu_dev
|
||||||
|
|
||||||
torch_dev = get_torch_device()
|
torch_dev = get_torch_device()
|
||||||
if vram_state == VRAMState.HIGH_VRAM or vram_state == VRAMState.SHARED:
|
if vram_state == VRAMState.HIGH_VRAM or vram_state == VRAMState.SHARED:
|
||||||
return torch_dev
|
return torch_dev
|
||||||
|
|
||||||
cpu_dev = torch.device("cpu")
|
|
||||||
if DISABLE_SMART_MEMORY or vram_state == VRAMState.NO_VRAM:
|
if DISABLE_SMART_MEMORY or vram_state == VRAMState.NO_VRAM:
|
||||||
return cpu_dev
|
return cpu_dev
|
||||||
|
|
||||||
@ -840,7 +845,7 @@ def unet_inital_load_device(parameters, dtype):
|
|||||||
|
|
||||||
mem_dev = get_free_memory(torch_dev)
|
mem_dev = get_free_memory(torch_dev)
|
||||||
mem_cpu = get_free_memory(cpu_dev)
|
mem_cpu = get_free_memory(cpu_dev)
|
||||||
if mem_dev > mem_cpu and model_size < mem_dev and comfy.memory_management.aimdo_enabled:
|
if mem_dev > mem_cpu and model_size < mem_dev:
|
||||||
return torch_dev
|
return torch_dev
|
||||||
else:
|
else:
|
||||||
return cpu_dev
|
return cpu_dev
|
||||||
@ -943,6 +948,9 @@ def text_encoder_device():
|
|||||||
return torch.device("cpu")
|
return torch.device("cpu")
|
||||||
|
|
||||||
def text_encoder_initial_device(load_device, offload_device, model_size=0):
|
def text_encoder_initial_device(load_device, offload_device, model_size=0):
|
||||||
|
if comfy.memory_management.aimdo_enabled:
|
||||||
|
return offload_device
|
||||||
|
|
||||||
if load_device == offload_device or model_size <= 1024 * 1024 * 1024:
|
if load_device == offload_device or model_size <= 1024 * 1024 * 1024:
|
||||||
return offload_device
|
return offload_device
|
||||||
|
|
||||||
|
|||||||
@ -241,6 +241,7 @@ class ModelPatcher:
|
|||||||
|
|
||||||
self.patches = {}
|
self.patches = {}
|
||||||
self.backup = {}
|
self.backup = {}
|
||||||
|
self.backup_buffers = {}
|
||||||
self.object_patches = {}
|
self.object_patches = {}
|
||||||
self.object_patches_backup = {}
|
self.object_patches_backup = {}
|
||||||
self.weight_wrapper_patches = {}
|
self.weight_wrapper_patches = {}
|
||||||
@ -306,10 +307,16 @@ class ModelPatcher:
|
|||||||
return self.model.lowvram_patch_counter
|
return self.model.lowvram_patch_counter
|
||||||
|
|
||||||
def get_free_memory(self, device):
|
def get_free_memory(self, device):
|
||||||
return comfy.model_management.get_free_memory(device)
|
#Prioritize batching (incl. CFG/conds etc) over keeping the model resident. In
|
||||||
|
#the vast majority of setups a little bit of offloading on the giant model more
|
||||||
|
#than pays for CFG. So return everything both torch and Aimdo could give us
|
||||||
|
aimdo_mem = 0
|
||||||
|
if comfy.memory_management.aimdo_enabled:
|
||||||
|
aimdo_mem = comfy_aimdo.model_vbar.vbars_analyze()
|
||||||
|
return comfy.model_management.get_free_memory(device) + aimdo_mem
|
||||||
|
|
||||||
def get_clone_model_override(self):
|
def get_clone_model_override(self):
|
||||||
return self.model, (self.backup, self.object_patches_backup, self.pinned)
|
return self.model, (self.backup, self.backup_buffers, self.object_patches_backup, self.pinned)
|
||||||
|
|
||||||
def clone(self, disable_dynamic=False, model_override=None):
|
def clone(self, disable_dynamic=False, model_override=None):
|
||||||
class_ = self.__class__
|
class_ = self.__class__
|
||||||
@ -336,7 +343,7 @@ class ModelPatcher:
|
|||||||
|
|
||||||
n.force_cast_weights = self.force_cast_weights
|
n.force_cast_weights = self.force_cast_weights
|
||||||
|
|
||||||
n.backup, n.object_patches_backup, n.pinned = model_override[1]
|
n.backup, n.backup_buffers, n.object_patches_backup, n.pinned = model_override[1]
|
||||||
|
|
||||||
# attachments
|
# attachments
|
||||||
n.attachments = {}
|
n.attachments = {}
|
||||||
@ -698,7 +705,7 @@ class ModelPatcher:
|
|||||||
for key in list(self.pinned):
|
for key in list(self.pinned):
|
||||||
self.unpin_weight(key)
|
self.unpin_weight(key)
|
||||||
|
|
||||||
def _load_list(self, prio_comfy_cast_weights=False, default_device=None):
|
def _load_list(self, for_dynamic=False, default_device=None):
|
||||||
loading = []
|
loading = []
|
||||||
for n, m in self.model.named_modules():
|
for n, m in self.model.named_modules():
|
||||||
default = False
|
default = False
|
||||||
@ -726,8 +733,13 @@ class ModelPatcher:
|
|||||||
return 0
|
return 0
|
||||||
module_offload_mem += check_module_offload_mem("{}.weight".format(n))
|
module_offload_mem += check_module_offload_mem("{}.weight".format(n))
|
||||||
module_offload_mem += check_module_offload_mem("{}.bias".format(n))
|
module_offload_mem += check_module_offload_mem("{}.bias".format(n))
|
||||||
prepend = (not hasattr(m, "comfy_cast_weights"),) if prio_comfy_cast_weights else ()
|
# Dynamic: small weights (<64KB) first, then larger weights prioritized by size.
|
||||||
loading.append(prepend + (module_offload_mem, module_mem, n, m, params))
|
# Non-dynamic: prioritize by module offload cost.
|
||||||
|
if for_dynamic:
|
||||||
|
sort_criteria = (module_offload_mem >= 64 * 1024, -module_offload_mem)
|
||||||
|
else:
|
||||||
|
sort_criteria = (module_offload_mem,)
|
||||||
|
loading.append(sort_criteria + (module_mem, n, m, params))
|
||||||
return loading
|
return loading
|
||||||
|
|
||||||
def load(self, device_to=None, lowvram_model_memory=0, force_patch_weights=False, full_load=False):
|
def load(self, device_to=None, lowvram_model_memory=0, force_patch_weights=False, full_load=False):
|
||||||
@ -1459,12 +1471,6 @@ class ModelPatcherDynamic(ModelPatcher):
|
|||||||
vbar = self._vbar_get()
|
vbar = self._vbar_get()
|
||||||
return (vbar.loaded_size() if vbar is not None else 0) + self.model.model_loaded_weight_memory
|
return (vbar.loaded_size() if vbar is not None else 0) + self.model.model_loaded_weight_memory
|
||||||
|
|
||||||
def get_free_memory(self, device):
|
|
||||||
#NOTE: on high condition / batch counts, estimate should have already vacated
|
|
||||||
#all non-dynamic models so this is safe even if its not 100% true that this
|
|
||||||
#would all be avaiable for inference use.
|
|
||||||
return comfy.model_management.get_total_memory(device) - self.model_size()
|
|
||||||
|
|
||||||
#Pinning is deferred to ops time. Assert against this API to avoid pin leaks.
|
#Pinning is deferred to ops time. Assert against this API to avoid pin leaks.
|
||||||
|
|
||||||
def pin_weight_to_device(self, key):
|
def pin_weight_to_device(self, key):
|
||||||
@ -1507,11 +1513,11 @@ class ModelPatcherDynamic(ModelPatcher):
|
|||||||
if vbar is not None:
|
if vbar is not None:
|
||||||
vbar.prioritize()
|
vbar.prioritize()
|
||||||
|
|
||||||
loading = self._load_list(prio_comfy_cast_weights=True, default_device=device_to)
|
loading = self._load_list(for_dynamic=True, default_device=device_to)
|
||||||
loading.sort(reverse=True)
|
loading.sort()
|
||||||
|
|
||||||
for x in loading:
|
for x in loading:
|
||||||
_, _, _, n, m, params = x
|
*_, module_mem, n, m, params = x
|
||||||
|
|
||||||
def set_dirty(item, dirty):
|
def set_dirty(item, dirty):
|
||||||
if dirty or not hasattr(item, "_v_signature"):
|
if dirty or not hasattr(item, "_v_signature"):
|
||||||
@ -1579,11 +1585,22 @@ class ModelPatcherDynamic(ModelPatcher):
|
|||||||
weight, _, _ = get_key_weight(self.model, key)
|
weight, _, _ = get_key_weight(self.model, key)
|
||||||
if key not in self.backup:
|
if key not in self.backup:
|
||||||
self.backup[key] = collections.namedtuple('Dimension', ['weight', 'inplace_update'])(weight, False)
|
self.backup[key] = collections.namedtuple('Dimension', ['weight', 'inplace_update'])(weight, False)
|
||||||
comfy.utils.set_attr_param(self.model, key, weight.to(device=device_to))
|
model_dtype = getattr(m, param + "_comfy_model_dtype", None)
|
||||||
self.model.model_loaded_weight_memory += weight.numel() * weight.element_size()
|
casted_weight = weight.to(dtype=model_dtype, device=device_to)
|
||||||
|
comfy.utils.set_attr_param(self.model, key, casted_weight)
|
||||||
|
self.model.model_loaded_weight_memory += casted_weight.numel() * casted_weight.element_size()
|
||||||
|
|
||||||
move_weight_functions(m, device_to)
|
move_weight_functions(m, device_to)
|
||||||
|
|
||||||
|
for key, buf in self.model.named_buffers(recurse=True):
|
||||||
|
if key not in self.backup_buffers:
|
||||||
|
self.backup_buffers[key] = buf
|
||||||
|
module, buf_name = comfy.utils.resolve_attr(self.model, key)
|
||||||
|
model_dtype = getattr(module, buf_name + "_comfy_model_dtype", None)
|
||||||
|
casted_buf = buf.to(dtype=model_dtype, device=device_to)
|
||||||
|
comfy.utils.set_attr_buffer(self.model, key, casted_buf)
|
||||||
|
self.model.model_loaded_weight_memory += casted_buf.numel() * casted_buf.element_size()
|
||||||
|
|
||||||
force_load_stat = f" Force pre-loaded {len(self.backup)} weights: {self.model.model_loaded_weight_memory // 1024} KB." if len(self.backup) > 0 else ""
|
force_load_stat = f" Force pre-loaded {len(self.backup)} weights: {self.model.model_loaded_weight_memory // 1024} KB." if len(self.backup) > 0 else ""
|
||||||
logging.info(f"Model {self.model.__class__.__name__} prepared for dynamic VRAM loading. {allocated_size // (1024 ** 2)}MB Staged. {num_patches} patches attached.{force_load_stat}")
|
logging.info(f"Model {self.model.__class__.__name__} prepared for dynamic VRAM loading. {allocated_size // (1024 ** 2)}MB Staged. {num_patches} patches attached.{force_load_stat}")
|
||||||
|
|
||||||
@ -1607,15 +1624,17 @@ class ModelPatcherDynamic(ModelPatcher):
|
|||||||
for key in list(self.backup.keys()):
|
for key in list(self.backup.keys()):
|
||||||
bk = self.backup.pop(key)
|
bk = self.backup.pop(key)
|
||||||
comfy.utils.set_attr_param(self.model, key, bk.weight)
|
comfy.utils.set_attr_param(self.model, key, bk.weight)
|
||||||
|
for key in list(self.backup_buffers.keys()):
|
||||||
|
comfy.utils.set_attr_buffer(self.model, key, self.backup_buffers.pop(key))
|
||||||
freed += self.model.model_loaded_weight_memory
|
freed += self.model.model_loaded_weight_memory
|
||||||
self.model.model_loaded_weight_memory = 0
|
self.model.model_loaded_weight_memory = 0
|
||||||
|
|
||||||
return freed
|
return freed
|
||||||
|
|
||||||
def partially_unload_ram(self, ram_to_unload):
|
def partially_unload_ram(self, ram_to_unload):
|
||||||
loading = self._load_list(prio_comfy_cast_weights=True, default_device=self.offload_device)
|
loading = self._load_list(for_dynamic=True, default_device=self.offload_device)
|
||||||
for x in loading:
|
for x in loading:
|
||||||
_, _, _, _, m, _ = x
|
*_, m, _ = x
|
||||||
ram_to_unload -= comfy.pinned_memory.unpin_memory(m)
|
ram_to_unload -= comfy.pinned_memory.unpin_memory(m)
|
||||||
if ram_to_unload <= 0:
|
if ram_to_unload <= 0:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -269,8 +269,8 @@ def uncast_bias_weight(s, weight, bias, offload_stream):
|
|||||||
return
|
return
|
||||||
os, weight_a, bias_a = offload_stream
|
os, weight_a, bias_a = offload_stream
|
||||||
device=None
|
device=None
|
||||||
#FIXME: This is not good RTTI
|
#FIXME: This is really bad RTTI
|
||||||
if not isinstance(weight_a, torch.Tensor):
|
if weight_a is not None and not isinstance(weight_a, torch.Tensor):
|
||||||
comfy_aimdo.model_vbar.vbar_unpin(s._v)
|
comfy_aimdo.model_vbar.vbar_unpin(s._v)
|
||||||
device = weight_a
|
device = weight_a
|
||||||
if os is None:
|
if os is None:
|
||||||
|
|||||||
@ -428,7 +428,7 @@ class CLIP:
|
|||||||
def generate(self, tokens, do_sample=True, max_length=256, temperature=1.0, top_k=50, top_p=0.95, min_p=0.0, repetition_penalty=1.0, seed=None):
|
def generate(self, tokens, do_sample=True, max_length=256, temperature=1.0, top_k=50, top_p=0.95, min_p=0.0, repetition_penalty=1.0, seed=None):
|
||||||
self.cond_stage_model.reset_clip_options()
|
self.cond_stage_model.reset_clip_options()
|
||||||
|
|
||||||
self.load_model()
|
self.load_model(tokens)
|
||||||
self.cond_stage_model.set_clip_options({"layer": None})
|
self.cond_stage_model.set_clip_options({"layer": None})
|
||||||
self.cond_stage_model.set_clip_options({"execution_device": self.patcher.load_device})
|
self.cond_stage_model.set_clip_options({"execution_device": self.patcher.load_device})
|
||||||
return self.cond_stage_model.generate(tokens, do_sample=do_sample, max_length=max_length, temperature=temperature, top_k=top_k, top_p=top_p, min_p=min_p, repetition_penalty=repetition_penalty, seed=seed)
|
return self.cond_stage_model.generate(tokens, do_sample=do_sample, max_length=max_length, temperature=temperature, top_k=top_k, top_p=top_p, min_p=min_p, repetition_penalty=repetition_penalty, seed=seed)
|
||||||
|
|||||||
@ -869,20 +869,31 @@ def safetensors_header(safetensors_path, max_size=100*1024*1024):
|
|||||||
|
|
||||||
ATTR_UNSET={}
|
ATTR_UNSET={}
|
||||||
|
|
||||||
def set_attr(obj, attr, value):
|
def resolve_attr(obj, attr):
|
||||||
attrs = attr.split(".")
|
attrs = attr.split(".")
|
||||||
for name in attrs[:-1]:
|
for name in attrs[:-1]:
|
||||||
obj = getattr(obj, name)
|
obj = getattr(obj, name)
|
||||||
prev = getattr(obj, attrs[-1], ATTR_UNSET)
|
return obj, attrs[-1]
|
||||||
|
|
||||||
|
def set_attr(obj, attr, value):
|
||||||
|
obj, name = resolve_attr(obj, attr)
|
||||||
|
prev = getattr(obj, name, ATTR_UNSET)
|
||||||
if value is ATTR_UNSET:
|
if value is ATTR_UNSET:
|
||||||
delattr(obj, attrs[-1])
|
delattr(obj, name)
|
||||||
else:
|
else:
|
||||||
setattr(obj, attrs[-1], value)
|
setattr(obj, name, value)
|
||||||
return prev
|
return prev
|
||||||
|
|
||||||
def set_attr_param(obj, attr, value):
|
def set_attr_param(obj, attr, value):
|
||||||
return set_attr(obj, attr, torch.nn.Parameter(value, requires_grad=False))
|
return set_attr(obj, attr, torch.nn.Parameter(value, requires_grad=False))
|
||||||
|
|
||||||
|
def set_attr_buffer(obj, attr, value):
|
||||||
|
obj, name = resolve_attr(obj, attr)
|
||||||
|
prev = getattr(obj, name, ATTR_UNSET)
|
||||||
|
persistent = name not in getattr(obj, "_non_persistent_buffers_set", set())
|
||||||
|
obj.register_buffer(name, value, persistent=persistent)
|
||||||
|
return prev
|
||||||
|
|
||||||
def copy_to_param(obj, attr, value):
|
def copy_to_param(obj, attr, value):
|
||||||
# inplace update tensor instead of replacing it
|
# inplace update tensor instead of replacing it
|
||||||
attrs = attr.split(".")
|
attrs = attr.split(".")
|
||||||
|
|||||||
@ -401,6 +401,7 @@ class VideoFromComponents(VideoInput):
|
|||||||
codec: VideoCodec = VideoCodec.AUTO,
|
codec: VideoCodec = VideoCodec.AUTO,
|
||||||
metadata: Optional[dict] = None,
|
metadata: Optional[dict] = None,
|
||||||
):
|
):
|
||||||
|
"""Save the video to a file path or BytesIO buffer."""
|
||||||
if format != VideoContainer.AUTO and format != VideoContainer.MP4:
|
if format != VideoContainer.AUTO and format != VideoContainer.MP4:
|
||||||
raise ValueError("Only MP4 format is supported for now")
|
raise ValueError("Only MP4 format is supported for now")
|
||||||
if codec != VideoCodec.AUTO and codec != VideoCodec.H264:
|
if codec != VideoCodec.AUTO and codec != VideoCodec.H264:
|
||||||
@ -408,6 +409,10 @@ class VideoFromComponents(VideoInput):
|
|||||||
extra_kwargs = {}
|
extra_kwargs = {}
|
||||||
if isinstance(format, VideoContainer) and format != VideoContainer.AUTO:
|
if isinstance(format, VideoContainer) and format != VideoContainer.AUTO:
|
||||||
extra_kwargs["format"] = format.value
|
extra_kwargs["format"] = format.value
|
||||||
|
elif isinstance(path, io.BytesIO):
|
||||||
|
# BytesIO has no file extension, so av.open can't infer the format.
|
||||||
|
# Default to mp4 since that's the only supported format anyway.
|
||||||
|
extra_kwargs["format"] = "mp4"
|
||||||
with av.open(path, mode='w', options={'movflags': 'use_metadata_tags'}, **extra_kwargs) as output:
|
with av.open(path, mode='w', options={'movflags': 'use_metadata_tags'}, **extra_kwargs) as output:
|
||||||
# Add metadata before writing any streams
|
# Add metadata before writing any streams
|
||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
|
|||||||
@ -1240,6 +1240,19 @@ class BoundingBox(ComfyTypeIO):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
@comfytype(io_type="CURVE")
|
||||||
|
class Curve(ComfyTypeIO):
|
||||||
|
CurvePoint = tuple[float, float]
|
||||||
|
Type = list[CurvePoint]
|
||||||
|
|
||||||
|
class Input(WidgetInput):
|
||||||
|
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None,
|
||||||
|
socketless: bool=True, default: list[tuple[float, float]]=None, advanced: bool=None):
|
||||||
|
super().__init__(id, display_name, optional, tooltip, None, default, socketless, None, None, None, None, advanced)
|
||||||
|
if default is None:
|
||||||
|
self.default = [(0.0, 0.0), (1.0, 1.0)]
|
||||||
|
|
||||||
|
|
||||||
DYNAMIC_INPUT_LOOKUP: dict[str, Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]] = {}
|
DYNAMIC_INPUT_LOOKUP: dict[str, Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]] = {}
|
||||||
def register_dynamic_input_func(io_type: str, func: Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]):
|
def register_dynamic_input_func(io_type: str, func: Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]):
|
||||||
DYNAMIC_INPUT_LOOKUP[io_type] = func
|
DYNAMIC_INPUT_LOOKUP[io_type] = func
|
||||||
@ -2226,5 +2239,6 @@ __all__ = [
|
|||||||
"PriceBadgeDepends",
|
"PriceBadgeDepends",
|
||||||
"PriceBadge",
|
"PriceBadge",
|
||||||
"BoundingBox",
|
"BoundingBox",
|
||||||
|
"Curve",
|
||||||
"NodeReplace",
|
"NodeReplace",
|
||||||
]
|
]
|
||||||
|
|||||||
14
execution.py
14
execution.py
@ -876,12 +876,14 @@ async def validate_inputs(prompt_id, prompt, item, validated):
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
# Unwraps values wrapped in __value__ key. This is used to pass
|
# Unwraps values wrapped in __value__ key or typed wrapper.
|
||||||
# list widget value to execution, as by default list value is
|
# This is used to pass list widget values to execution,
|
||||||
# reserved to represent the connection between nodes.
|
# as by default list value is reserved to represent the
|
||||||
if isinstance(val, dict) and "__value__" in val:
|
# connection between nodes.
|
||||||
val = val["__value__"]
|
if isinstance(val, dict):
|
||||||
inputs[x] = val
|
if "__value__" in val:
|
||||||
|
val = val["__value__"]
|
||||||
|
inputs[x] = val
|
||||||
|
|
||||||
if input_type == "INT":
|
if input_type == "INT":
|
||||||
val = int(val)
|
val = int(val)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ alembic
|
|||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
av>=14.2.0
|
av>=14.2.0
|
||||||
comfy-kitchen>=0.2.7
|
comfy-kitchen>=0.2.7
|
||||||
comfy-aimdo>=0.2.4
|
comfy-aimdo>=0.2.6
|
||||||
requests
|
requests
|
||||||
|
|
||||||
#non essential dependencies:
|
#non essential dependencies:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user