mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-25 09:52:35 +08:00
Some checks are pending
Build package / Build Test (3.10) (push) Waiting to run
Build package / Build Test (3.12) (push) Waiting to run
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Build package / Build Test (3.11) (push) Waiting to run
Build package / Build Test (3.13) (push) Waiting to run
Build package / Build Test (3.14) (push) Waiting to run
* fix: pin SQLAlchemy>=2.0 in requirements.txt (fixes #13036) (#13316) * Refactor io to IO in nodes_ace.py (#13485) * Bump comfyui-frontend-package to 1.42.12 (#13489) * Make the ltx audio vae more native. (#13486) * feat(api-nodes): add automatic downscaling of videos for ByteDance 2 nodes (#13465) * Support standalone LTXV audio VAEs (#13499) * [Partner Nodes] added 4K resolution for Veo models; added Veo 3 Lite model (#13330) * feat(api nodes): added 4K resolution for Veo models; added Veo 3 Lite model Signed-off-by: bigcat88 <bigcat88@icloud.com> * increase poll_interval from 5 to 9 --------- Signed-off-by: bigcat88 <bigcat88@icloud.com> Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> * Bump comfyui-frontend-package to 1.42.14 (#13493) * Add gpt-image-2 as version option (#13501) * Allow logging in comfy app files. (#13505) * chore: update workflow templates to v0.9.59 (#13507) * fix(veo): reject 4K resolution for veo-3.0 models in Veo3VideoGenerationNode (#13504) The tooltip on the resolution input states that 4K is not available for veo-3.1-lite or veo-3.0 models, but the execute guard only rejected the lite combination. Selecting 4K with veo-3.0-generate-001 or veo-3.0-fast-generate-001 would fall through and hit the upstream API with an invalid request. Broaden the guard to match the documented behavior and update the error message accordingly. Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> * feat: RIFE and FILM frame interpolation model support (CORE-29) (#13258) * initial RIFE support * Also support FILM * Better RAM usage, reduce FILM VRAM peak * Add model folder placeholder * Fix oom fallback frame loss * Remove torch.compile for now * Rename model input * Shorter input type name --------- * fix: use Parameter assignment for Stable_Zero123 cc_projection weights (fixes #13492) (#13518) On Windows with aimdo enabled, disable_weight_init.Linear uses lazy initialization that sets weight and bias to None to avoid unnecessary memory allocation. This caused a crash when copy_() was called on the None weight attribute in Stable_Zero123.__init__. Replace copy_() with direct torch.nn.Parameter assignment, which works correctly on both Windows (aimdo enabled) and other platforms. * Derive InterruptProcessingException from BaseException (#13523) * bump manager version to 4.2.1 (#13516) * ModelPatcherDynamic: force cast stray weights on comfy layers (#13487) the mixed_precision ops can have input_scale parameters that are used in tensor math but arent a weight or bias so dont get proper VRAM management. Treat these as force-castable parameters like the non comfy weight, random params are buffers already are. * Update logging level for invalid version format (#13526) * [Partner Nodes] add SD2 real human support (#13509) * feat(api-nodes): add SD2 real human support Signed-off-by: bigcat88 <bigcat88@icloud.com> * fix: add validation before uploading Assets Signed-off-by: bigcat88 <bigcat88@icloud.com> * Add asset_id and group_id displaying on the node Signed-off-by: bigcat88 <bigcat88@icloud.com> * extend poll_op to use instead of custom async cycle Signed-off-by: bigcat88 <bigcat88@icloud.com> * added the polling for the "Active" status after asset creation Signed-off-by: bigcat88 <bigcat88@icloud.com> * updated tooltip for group_id * allow usage of real human in the ByteDance2FirstLastFrame node * add reference count limits * corrected price in status when input assets contain video Signed-off-by: bigcat88 <bigcat88@icloud.com> --------- Signed-off-by: bigcat88 <bigcat88@icloud.com> * feat: SAM (segment anything) 3.1 support (CORE-34) (#13408) * [Partner Nodes] GPTImage: fix price badges, add new resolutions (#13519) * fix(api-nodes): fixed price badges, add new resolutions Signed-off-by: bigcat88 <bigcat88@icloud.com> * proper calculate the total run cost when "n > 1" Signed-off-by: bigcat88 <bigcat88@icloud.com> --------- Signed-off-by: bigcat88 <bigcat88@icloud.com> * chore: update workflow templates to v0.9.61 (#13533) * chore: update embedded docs to v0.4.4 (#13535) * add 4K resolution to Kling nodes (#13536) Signed-off-by: bigcat88 <bigcat88@icloud.com> * Fix LTXV Reference Audio node (#13531) * comfy-aimdo 0.2.14: Hotfix async allocator estimations (#13534) This was doing an over-estimate of VRAM used by the async allocator when lots of little small tensors were in play. Also change the versioning scheme to == so we can roll forward aimdo without worrying about stable regressions downstream in comfyUI core. * Disable sageattention for SAM3 (#13529) Causes Nans * execution: Add anti-cycle validation (#13169) Currently if the graph contains a cycle, the just inifitiate recursions, hits a catch all then throws a generic error against the output node that seeded the validation. Instead, fail the offending cycling mode chain and handlng it as an error in its own right. Co-authored-by: guill <jacob.e.segal@gmail.com> * chore: update workflow templates to v0.9.62 (#13539) --------- Signed-off-by: bigcat88 <bigcat88@icloud.com> Co-authored-by: Octopus <liyuan851277048@icloud.com> Co-authored-by: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com> Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com> Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com> Co-authored-by: Jukka Seppänen <40791699+kijai@users.noreply.github.com> Co-authored-by: AustinMroz <austin@comfy.org> Co-authored-by: Daxiong (Lin) <contact@comfyui-wiki.com> Co-authored-by: Matt Miller <matt@miller-media.com> Co-authored-by: blepping <157360029+blepping@users.noreply.github.com> Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com> Co-authored-by: rattus <46076784+rattus128@users.noreply.github.com> Co-authored-by: guill <jacob.e.segal@gmail.com>
212 lines
8.6 KiB
Python
212 lines
8.6 KiB
Python
import torch
|
|
from tqdm import tqdm
|
|
from typing_extensions import override
|
|
|
|
import comfy.model_patcher
|
|
import comfy.utils
|
|
import folder_paths
|
|
from comfy import model_management
|
|
from comfy_extras.frame_interpolation_models.ifnet import IFNet, detect_rife_config
|
|
from comfy_extras.frame_interpolation_models.film_net import FILMNet
|
|
from comfy_api.latest import ComfyExtension, io
|
|
|
|
FrameInterpolationModel = io.Custom("INTERP_MODEL")
|
|
|
|
|
|
class FrameInterpolationModelLoader(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="FrameInterpolationModelLoader",
|
|
display_name="Load Frame Interpolation Model",
|
|
category="loaders",
|
|
inputs=[
|
|
io.Combo.Input("model_name", options=folder_paths.get_filename_list("frame_interpolation"),
|
|
tooltip="Select a frame interpolation model to load. Models must be placed in the 'frame_interpolation' folder."),
|
|
],
|
|
outputs=[
|
|
FrameInterpolationModel.Output(),
|
|
],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, model_name) -> io.NodeOutput:
|
|
model_path = folder_paths.get_full_path_or_raise("frame_interpolation", model_name)
|
|
sd = comfy.utils.load_torch_file(model_path, safe_load=True)
|
|
|
|
model = cls._detect_and_load(sd)
|
|
dtype = torch.float16 if model_management.should_use_fp16(model_management.get_torch_device()) else torch.float32
|
|
model.eval().to(dtype)
|
|
patcher = comfy.model_patcher.ModelPatcher(
|
|
model,
|
|
load_device=model_management.get_torch_device(),
|
|
offload_device=model_management.unet_offload_device(),
|
|
)
|
|
return io.NodeOutput(patcher)
|
|
|
|
@classmethod
|
|
def _detect_and_load(cls, sd):
|
|
# Try FILM
|
|
if "extract.extract_sublevels.convs.0.0.conv.weight" in sd:
|
|
model = FILMNet()
|
|
model.load_state_dict(sd)
|
|
return model
|
|
|
|
# Try RIFE (needs key remapping for raw checkpoints)
|
|
sd = comfy.utils.state_dict_prefix_replace(sd, {"module.": "", "flownet.": ""})
|
|
key_map = {}
|
|
for k in sd:
|
|
for i in range(5):
|
|
if k.startswith(f"block{i}."):
|
|
key_map[k] = f"blocks.{i}.{k[len(f'block{i}.'):]}"
|
|
if key_map:
|
|
sd = {key_map.get(k, k): v for k, v in sd.items()}
|
|
sd = {k: v for k, v in sd.items() if not k.startswith(("teacher.", "caltime."))}
|
|
|
|
try:
|
|
head_ch, channels = detect_rife_config(sd)
|
|
except (KeyError, ValueError):
|
|
raise ValueError("Unrecognized frame interpolation model format")
|
|
model = IFNet(head_ch=head_ch, channels=channels)
|
|
model.load_state_dict(sd)
|
|
return model
|
|
|
|
|
|
class FrameInterpolate(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="FrameInterpolate",
|
|
display_name="Frame Interpolate",
|
|
category="image/video",
|
|
search_aliases=["rife", "film", "frame interpolation", "slow motion", "interpolate frames", "vfi"],
|
|
inputs=[
|
|
FrameInterpolationModel.Input("interp_model"),
|
|
io.Image.Input("images"),
|
|
io.Int.Input("multiplier", default=2, min=2, max=16),
|
|
],
|
|
outputs=[
|
|
io.Image.Output(),
|
|
],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, interp_model, images, multiplier) -> io.NodeOutput:
|
|
offload_device = model_management.intermediate_device()
|
|
|
|
num_frames = images.shape[0]
|
|
if num_frames < 2 or multiplier < 2:
|
|
return io.NodeOutput(images)
|
|
|
|
model_management.load_model_gpu(interp_model)
|
|
device = interp_model.load_device
|
|
dtype = interp_model.model_dtype()
|
|
inference_model = interp_model.model
|
|
|
|
# Free VRAM for inference activations (model weights + ~20x a single frame's worth)
|
|
H, W = images.shape[1], images.shape[2]
|
|
activation_mem = H * W * 3 * images.element_size() * 20
|
|
model_management.free_memory(activation_mem, device)
|
|
align = getattr(inference_model, "pad_align", 1)
|
|
|
|
# Prepare a single padded frame on device for determining output dimensions
|
|
def prepare_frame(idx):
|
|
frame = images[idx:idx + 1].movedim(-1, 1).to(dtype=dtype, device=device)
|
|
if align > 1:
|
|
from comfy.ldm.common_dit import pad_to_patch_size
|
|
frame = pad_to_patch_size(frame, (align, align), padding_mode="reflect")
|
|
return frame
|
|
|
|
# Count total interpolation passes for progress bar
|
|
total_pairs = num_frames - 1
|
|
num_interp = multiplier - 1
|
|
total_steps = total_pairs * num_interp
|
|
pbar = comfy.utils.ProgressBar(total_steps)
|
|
tqdm_bar = tqdm(total=total_steps, desc="Frame interpolation")
|
|
|
|
batch = num_interp # reduced on OOM and persists across pairs (same resolution = same limit)
|
|
t_values = [t / multiplier for t in range(1, multiplier)]
|
|
|
|
out_dtype = model_management.intermediate_dtype()
|
|
total_out_frames = total_pairs * multiplier + 1
|
|
result = torch.empty((total_out_frames, 3, H, W), dtype=out_dtype, device=offload_device)
|
|
result[0] = images[0].movedim(-1, 0).to(out_dtype)
|
|
out_idx = 1
|
|
|
|
# Pre-compute timestep tensor on device (padded dimensions needed)
|
|
sample = prepare_frame(0)
|
|
pH, pW = sample.shape[2], sample.shape[3]
|
|
ts_full = torch.tensor(t_values, device=device, dtype=dtype).reshape(num_interp, 1, 1, 1)
|
|
ts_full = ts_full.expand(-1, 1, pH, pW)
|
|
del sample
|
|
|
|
multi_fn = getattr(inference_model, "forward_multi_timestep", None)
|
|
feat_cache = {}
|
|
prev_frame = None
|
|
|
|
try:
|
|
for i in range(total_pairs):
|
|
img0_single = prev_frame if prev_frame is not None else prepare_frame(i)
|
|
img1_single = prepare_frame(i + 1)
|
|
prev_frame = img1_single
|
|
|
|
# Cache features: img1 of pair N becomes img0 of pair N+1
|
|
feat_cache["img0"] = feat_cache.pop("next") if "next" in feat_cache else inference_model.extract_features(img0_single)
|
|
feat_cache["img1"] = inference_model.extract_features(img1_single)
|
|
feat_cache["next"] = feat_cache["img1"]
|
|
|
|
used_multi = False
|
|
if multi_fn is not None:
|
|
# Models with timestep-independent flow can compute it once for all timesteps
|
|
try:
|
|
mids = multi_fn(img0_single, img1_single, t_values, cache=feat_cache)
|
|
result[out_idx:out_idx + num_interp] = mids[:, :, :H, :W].to(out_dtype)
|
|
out_idx += num_interp
|
|
pbar.update(num_interp)
|
|
tqdm_bar.update(num_interp)
|
|
used_multi = True
|
|
except model_management.OOM_EXCEPTION:
|
|
model_management.soft_empty_cache()
|
|
multi_fn = None # fall through to single-timestep path
|
|
|
|
if not used_multi:
|
|
j = 0
|
|
while j < num_interp:
|
|
b = min(batch, num_interp - j)
|
|
try:
|
|
img0 = img0_single.expand(b, -1, -1, -1)
|
|
img1 = img1_single.expand(b, -1, -1, -1)
|
|
mids = inference_model(img0, img1, timestep=ts_full[j:j + b], cache=feat_cache)
|
|
result[out_idx:out_idx + b] = mids[:, :, :H, :W].to(out_dtype)
|
|
out_idx += b
|
|
pbar.update(b)
|
|
tqdm_bar.update(b)
|
|
j += b
|
|
except model_management.OOM_EXCEPTION:
|
|
if batch <= 1:
|
|
raise
|
|
batch = max(1, batch // 2)
|
|
model_management.soft_empty_cache()
|
|
|
|
result[out_idx] = images[i + 1].movedim(-1, 0).to(out_dtype)
|
|
out_idx += 1
|
|
finally:
|
|
tqdm_bar.close()
|
|
|
|
# BCHW -> BHWC
|
|
result = result.movedim(1, -1).clamp_(0.0, 1.0)
|
|
return io.NodeOutput(result)
|
|
|
|
|
|
class FrameInterpolationExtension(ComfyExtension):
|
|
@override
|
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
|
return [
|
|
FrameInterpolationModelLoader,
|
|
FrameInterpolate,
|
|
]
|
|
|
|
|
|
async def comfy_entrypoint() -> FrameInterpolationExtension:
|
|
return FrameInterpolationExtension()
|