Merge branch 'master' into feature/generic-feature-flag-cli

This commit is contained in:
Jedrzej Kosinski 2026-05-04 18:17:50 -07:00 committed by GitHub
commit 0b5da2af97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 73 additions and 45 deletions

View File

@ -721,13 +721,15 @@ def load_models_gpu(models, memory_required=0, force_patch_weights=False, minimu
else: else:
minimum_memory_required = max(inference_memory, minimum_memory_required + extra_reserved_memory()) minimum_memory_required = max(inference_memory, minimum_memory_required + extra_reserved_memory())
models_temp = set() # Order-preserving dedup. A plain set() would randomize iteration order across runs
models_temp = {}
for m in models: for m in models:
models_temp.add(m) models_temp[m] = None
for mm in m.model_patches_models(): for mm in m.model_patches_models():
models_temp.add(mm) models_temp[mm] = None
models = models_temp models = list(models_temp)
models.reverse()
models_to_load = [] models_to_load = []

View File

@ -37,7 +37,8 @@ def prefetch_queue_pop(queue, device, module):
consumed = queue.pop(0) consumed = queue.pop(0)
if consumed is not None: if consumed is not None:
offload_stream, prefetch_state = consumed offload_stream, prefetch_state = consumed
offload_stream.wait_stream(comfy.model_management.current_stream(device)) if offload_stream is not None:
offload_stream.wait_stream(comfy.model_management.current_stream(device))
_, comfy_modules = prefetch_state _, comfy_modules = prefetch_state
if comfy_modules is not None: if comfy_modules is not None:
cleanup_prefetched_modules(comfy_modules) cleanup_prefetched_modules(comfy_modules)

View File

@ -253,6 +253,9 @@ def resolve_cast_module_with_vbar(s, dtype, device, bias_dtype, compute_dtype, w
if bias is not None: if bias is not None:
bias = post_cast(s, "bias", bias, bias_dtype, prefetch["resident"], update_weight) bias = post_cast(s, "bias", bias, bias_dtype, prefetch["resident"], update_weight)
if prefetch["signature"] is not None:
prefetch["resident"] = True
return weight, bias return weight, bias

View File

@ -89,7 +89,8 @@ def get_additional_models(conds, dtype):
gligen += get_models_from_cond(conds[k], "gligen") gligen += get_models_from_cond(conds[k], "gligen")
add_models += get_models_from_cond(conds[k], "additional_models") add_models += get_models_from_cond(conds[k], "additional_models")
control_nets = set(cnets) # Order-preserving dedup. A plain set() would randomize iteration order across runs
control_nets = list(dict.fromkeys(cnets))
inference_memory = 0 inference_memory = 0
control_models = [] control_models = []

View File

@ -33,7 +33,7 @@ class OpenAIVideoSora2(IO.ComfyNode):
def define_schema(cls): def define_schema(cls):
return IO.Schema( return IO.Schema(
node_id="OpenAIVideoSora2", node_id="OpenAIVideoSora2",
display_name="OpenAI Sora - Video (Deprecated)", display_name="OpenAI Sora - Video (DEPRECATED)",
category="api node/video/Sora", category="api node/video/Sora",
description=( description=(
"OpenAI video and audio generation.\n\n" "OpenAI video and audio generation.\n\n"

View File

@ -199,6 +199,9 @@ class FILMNet(nn.Module):
def get_dtype(self): def get_dtype(self):
return self.extract.extract_sublevels.convs[0][0].conv.weight.dtype return self.extract.extract_sublevels.convs[0][0].conv.weight.dtype
def memory_used_forward(self, shape, dtype):
return 1700 * shape[1] * shape[2] * dtype.itemsize
def _build_warp_grids(self, H, W, device): def _build_warp_grids(self, H, W, device):
"""Pre-compute warp grids for all pyramid levels.""" """Pre-compute warp grids for all pyramid levels."""
if (H, W) in self._warp_grids: if (H, W) in self._warp_grids:

View File

@ -74,6 +74,9 @@ class IFNet(nn.Module):
def get_dtype(self): def get_dtype(self):
return self.encode.cnn0.weight.dtype return self.encode.cnn0.weight.dtype
def memory_used_forward(self, shape, dtype):
return 300 * shape[1] * shape[2] * dtype.itemsize
def _build_warp_grids(self, H, W, device): def _build_warp_grids(self, H, W, device):
if (H, W) in self._warp_grids: if (H, W) in self._warp_grids:
return return

View File

@ -37,7 +37,7 @@ class FrameInterpolationModelLoader(io.ComfyNode):
model = cls._detect_and_load(sd) model = cls._detect_and_load(sd)
dtype = torch.float16 if model_management.should_use_fp16(model_management.get_torch_device()) else torch.float32 dtype = torch.float16 if model_management.should_use_fp16(model_management.get_torch_device()) else torch.float32
model.eval().to(dtype) model.eval().to(dtype)
patcher = comfy.model_patcher.ModelPatcher( patcher = comfy.model_patcher.CoreModelPatcher(
model, model,
load_device=model_management.get_torch_device(), load_device=model_management.get_torch_device(),
offload_device=model_management.unet_offload_device(), offload_device=model_management.unet_offload_device(),
@ -78,7 +78,7 @@ class FrameInterpolate(io.ComfyNode):
return io.Schema( return io.Schema(
node_id="FrameInterpolate", node_id="FrameInterpolate",
display_name="Frame Interpolate", display_name="Frame Interpolate",
category="image/video", category="video",
search_aliases=["rife", "film", "frame interpolation", "slow motion", "interpolate frames", "vfi"], search_aliases=["rife", "film", "frame interpolation", "slow motion", "interpolate frames", "vfi"],
inputs=[ inputs=[
FrameInterpolationModel.Input("interp_model"), FrameInterpolationModel.Input("interp_model"),
@ -98,16 +98,13 @@ class FrameInterpolate(io.ComfyNode):
if num_frames < 2 or multiplier < 2: if num_frames < 2 or multiplier < 2:
return io.NodeOutput(images) return io.NodeOutput(images)
model_management.load_model_gpu(interp_model)
device = interp_model.load_device device = interp_model.load_device
dtype = interp_model.model_dtype() dtype = interp_model.model_dtype()
inference_model = interp_model.model inference_model = interp_model.model
activation_mem = inference_model.memory_used_forward(images.shape, dtype)
# Free VRAM for inference activations (model weights + ~20x a single frame's worth) model_management.load_models_gpu([interp_model], memory_required=activation_mem)
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) align = getattr(inference_model, "pad_align", 1)
H, W = images.shape[1], images.shape[2]
# Prepare a single padded frame on device for determining output dimensions # Prepare a single padded frame on device for determining output dimensions
def prepare_frame(idx): def prepare_frame(idx):

View File

@ -11,7 +11,7 @@ class ImageCompare(IO.ComfyNode):
def define_schema(cls): def define_schema(cls):
return IO.Schema( return IO.Schema(
node_id="ImageCompare", node_id="ImageCompare",
display_name="Image Compare", display_name="Compare Images",
description="Compares two images side by side with a slider.", description="Compares two images side by side with a slider.",
category="image", category="image",
essentials_category="Image Tools", essentials_category="Image Tools",

View File

@ -24,7 +24,7 @@ class ImageCrop(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ImageCrop", node_id="ImageCrop",
search_aliases=["trim"], search_aliases=["trim"],
display_name="Image Crop (Deprecated)", display_name="Crop Image (DEPRECATED)",
category="image/transform", category="image/transform",
is_deprecated=True, is_deprecated=True,
essentials_category="Image Tools", essentials_category="Image Tools",
@ -56,7 +56,7 @@ class ImageCropV2(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ImageCropV2", node_id="ImageCropV2",
search_aliases=["trim"], search_aliases=["trim"],
display_name="Image Crop", display_name="Crop Image",
category="image/transform", category="image/transform",
essentials_category="Image Tools", essentials_category="Image Tools",
has_intermediate_output=True, has_intermediate_output=True,
@ -109,6 +109,7 @@ class RepeatImageBatch(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="RepeatImageBatch", node_id="RepeatImageBatch",
search_aliases=["duplicate image", "clone image"], search_aliases=["duplicate image", "clone image"],
display_name="Repeat Image Batch",
category="image/batch", category="image/batch",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
@ -131,6 +132,7 @@ class ImageFromBatch(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ImageFromBatch", node_id="ImageFromBatch",
search_aliases=["select image", "pick from batch", "extract image"], search_aliases=["select image", "pick from batch", "extract image"],
display_name="Get Image from Batch",
category="image/batch", category="image/batch",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
@ -157,7 +159,8 @@ class ImageAddNoise(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ImageAddNoise", node_id="ImageAddNoise",
search_aliases=["film grain"], search_aliases=["film grain"],
category="image", display_name="Add Noise to Image",
category="image/postprocessing",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
IO.Int.Input( IO.Int.Input(
@ -259,7 +262,7 @@ class ImageStitch(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ImageStitch", node_id="ImageStitch",
search_aliases=["combine images", "join images", "concatenate images", "side by side"], search_aliases=["combine images", "join images", "concatenate images", "side by side"],
display_name="Image Stitch", display_name="Stitch Images",
description="Stitches image2 to image1 in the specified direction.\n" description="Stitches image2 to image1 in the specified direction.\n"
"If image2 is not provided, returns image1 unchanged.\n" "If image2 is not provided, returns image1 unchanged.\n"
"Optional spacing can be added between images.", "Optional spacing can be added between images.",
@ -434,6 +437,7 @@ class ResizeAndPadImage(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ResizeAndPadImage", node_id="ResizeAndPadImage",
search_aliases=["fit to size"], search_aliases=["fit to size"],
display_name="Resize And Pad Image",
category="image/transform", category="image/transform",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
@ -485,6 +489,7 @@ class SaveSVGNode(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="SaveSVGNode", node_id="SaveSVGNode",
search_aliases=["export vector", "save vector graphics"], search_aliases=["export vector", "save vector graphics"],
display_name="Save SVG",
description="Save SVG files on disk.", description="Save SVG files on disk.",
category="image/save", category="image/save",
inputs=[ inputs=[
@ -591,7 +596,7 @@ class ImageRotate(IO.ComfyNode):
def define_schema(cls): def define_schema(cls):
return IO.Schema( return IO.Schema(
node_id="ImageRotate", node_id="ImageRotate",
display_name="Image Rotate", display_name="Rotate Image",
search_aliases=["turn", "flip orientation"], search_aliases=["turn", "flip orientation"],
category="image/transform", category="image/transform",
essentials_category="Image Tools", essentials_category="Image Tools",
@ -624,6 +629,7 @@ class ImageFlip(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="ImageFlip", node_id="ImageFlip",
search_aliases=["mirror", "reflect"], search_aliases=["mirror", "reflect"],
display_name="Flip Image",
category="image/transform", category="image/transform",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
@ -650,6 +656,7 @@ class ImageScaleToMaxDimension(IO.ComfyNode):
def define_schema(cls): def define_schema(cls):
return IO.Schema( return IO.Schema(
node_id="ImageScaleToMaxDimension", node_id="ImageScaleToMaxDimension",
display_name="Scale Image to Max Dimension",
category="image/upscaling", category="image/upscaling",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),

View File

@ -80,7 +80,8 @@ class ImageCompositeMasked(IO.ComfyNode):
def define_schema(cls): def define_schema(cls):
return IO.Schema( return IO.Schema(
node_id="ImageCompositeMasked", node_id="ImageCompositeMasked",
search_aliases=["paste image", "overlay", "layer"], search_aliases=["overlay", "layer", "paste image", "images composition"],
display_name="Image Composite Masked",
category="image", category="image",
inputs=[ inputs=[
IO.Image.Input("destination"), IO.Image.Input("destination"),
@ -201,6 +202,7 @@ class InvertMask(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="InvertMask", node_id="InvertMask",
search_aliases=["reverse mask", "flip mask"], search_aliases=["reverse mask", "flip mask"],
display_name="Invert Mask",
category="mask", category="mask",
inputs=[ inputs=[
IO.Mask.Input("mask"), IO.Mask.Input("mask"),
@ -222,6 +224,7 @@ class CropMask(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="CropMask", node_id="CropMask",
search_aliases=["cut mask", "extract mask region", "mask slice"], search_aliases=["cut mask", "extract mask region", "mask slice"],
display_name="Crop Mask",
category="mask", category="mask",
inputs=[ inputs=[
IO.Mask.Input("mask"), IO.Mask.Input("mask"),
@ -247,7 +250,8 @@ class MaskComposite(IO.ComfyNode):
def define_schema(cls): def define_schema(cls):
return IO.Schema( return IO.Schema(
node_id="MaskComposite", node_id="MaskComposite",
search_aliases=["combine masks", "blend masks", "layer masks"], search_aliases=["combine masks", "blend masks", "layer masks", "masks composition"],
display_name="Combine Masks",
category="mask", category="mask",
inputs=[ inputs=[
IO.Mask.Input("destination"), IO.Mask.Input("destination"),
@ -298,6 +302,7 @@ class FeatherMask(IO.ComfyNode):
return IO.Schema( return IO.Schema(
node_id="FeatherMask", node_id="FeatherMask",
search_aliases=["soft edge mask", "blur mask edges", "gradient mask edge"], search_aliases=["soft edge mask", "blur mask edges", "gradient mask edge"],
display_name="Feather Mask",
category="mask", category="mask",
inputs=[ inputs=[
IO.Mask.Input("mask"), IO.Mask.Input("mask"),

View File

@ -59,7 +59,8 @@ class ImageRGBToYUV(io.ComfyNode):
return io.Schema( return io.Schema(
node_id="ImageRGBToYUV", node_id="ImageRGBToYUV",
search_aliases=["color space conversion"], search_aliases=["color space conversion"],
category="image/batch", display_name="Image RGB to YUV",
category="image/color",
inputs=[ inputs=[
io.Image.Input("image"), io.Image.Input("image"),
], ],
@ -81,7 +82,8 @@ class ImageYUVToRGB(io.ComfyNode):
return io.Schema( return io.Schema(
node_id="ImageYUVToRGB", node_id="ImageYUVToRGB",
search_aliases=["color space conversion"], search_aliases=["color space conversion"],
category="image/batch", display_name="Image YUV to RGB",
category="image/color",
inputs=[ inputs=[
io.Image.Input("Y"), io.Image.Input("Y"),
io.Image.Input("U"), io.Image.Input("U"),

View File

@ -20,7 +20,8 @@ class Blend(io.ComfyNode):
def define_schema(cls): def define_schema(cls):
return io.Schema( return io.Schema(
node_id="ImageBlend", node_id="ImageBlend",
display_name="Image Blend", search_aliases=["mix images"],
display_name="Blend Images",
category="image/postprocessing", category="image/postprocessing",
essentials_category="Image Tools", essentials_category="Image Tools",
inputs=[ inputs=[
@ -224,6 +225,7 @@ class ImageScaleToTotalPixels(io.ComfyNode):
def define_schema(cls): def define_schema(cls):
return io.Schema( return io.Schema(
node_id="ImageScaleToTotalPixels", node_id="ImageScaleToTotalPixels",
display_name="Scale Image to Total Pixels",
category="image/upscaling", category="image/upscaling",
inputs=[ inputs=[
io.Image.Input("image"), io.Image.Input("image"),
@ -568,7 +570,7 @@ class BatchImagesNode(io.ComfyNode):
return io.Schema( return io.Schema(
node_id="BatchImagesNode", node_id="BatchImagesNode",
display_name="Batch Images", display_name="Batch Images",
category="image", category="image/batch",
essentials_category="Image Tools", essentials_category="Image Tools",
search_aliases=["batch", "image batch", "batch images", "combine images", "merge images", "stack images"], search_aliases=["batch", "image batch", "batch images", "combine images", "merge images", "stack images"],
inputs=[ inputs=[

View File

@ -17,7 +17,8 @@ class SaveWEBM(io.ComfyNode):
return io.Schema( return io.Schema(
node_id="SaveWEBM", node_id="SaveWEBM",
search_aliases=["export webm"], search_aliases=["export webm"],
category="image/video", display_name="Save WEBM",
category="video",
is_experimental=True, is_experimental=True,
inputs=[ inputs=[
io.Image.Input("images"), io.Image.Input("images"),
@ -72,7 +73,7 @@ class SaveVideo(io.ComfyNode):
node_id="SaveVideo", node_id="SaveVideo",
search_aliases=["export video"], search_aliases=["export video"],
display_name="Save Video", display_name="Save Video",
category="image/video", category="video",
essentials_category="Basics", essentials_category="Basics",
description="Saves the input images to your ComfyUI output directory.", description="Saves the input images to your ComfyUI output directory.",
inputs=[ inputs=[
@ -121,7 +122,7 @@ class CreateVideo(io.ComfyNode):
node_id="CreateVideo", node_id="CreateVideo",
search_aliases=["images to video"], search_aliases=["images to video"],
display_name="Create Video", display_name="Create Video",
category="image/video", category="video",
description="Create a video from images.", description="Create a video from images.",
inputs=[ inputs=[
io.Image.Input("images", tooltip="The images to create a video from."), io.Image.Input("images", tooltip="The images to create a video from."),
@ -146,7 +147,7 @@ class GetVideoComponents(io.ComfyNode):
node_id="GetVideoComponents", node_id="GetVideoComponents",
search_aliases=["extract frames", "split video", "video to images", "demux"], search_aliases=["extract frames", "split video", "video to images", "demux"],
display_name="Get Video Components", display_name="Get Video Components",
category="image/video", category="video",
description="Extracts all components from a video: frames, audio, and framerate.", description="Extracts all components from a video: frames, audio, and framerate.",
inputs=[ inputs=[
io.Video.Input("video", tooltip="The video to extract components from."), io.Video.Input("video", tooltip="The video to extract components from."),
@ -174,7 +175,7 @@ class LoadVideo(io.ComfyNode):
node_id="LoadVideo", node_id="LoadVideo",
search_aliases=["import video", "open video", "video file"], search_aliases=["import video", "open video", "video file"],
display_name="Load Video", display_name="Load Video",
category="image/video", category="video",
essentials_category="Basics", essentials_category="Basics",
inputs=[ inputs=[
io.Combo.Input("file", options=sorted(files), upload=io.UploadType.video), io.Combo.Input("file", options=sorted(files), upload=io.UploadType.video),
@ -216,7 +217,7 @@ class VideoSlice(io.ComfyNode):
"frame load cap", "frame load cap",
"start time", "start time",
], ],
category="image/video", category="video",
essentials_category="Video Tools", essentials_category="Video Tools",
inputs=[ inputs=[
io.Video.Input("video"), io.Video.Input("video"),

View File

@ -1887,7 +1887,7 @@ class ImageInvert:
RETURN_TYPES = ("IMAGE",) RETURN_TYPES = ("IMAGE",)
FUNCTION = "invert" FUNCTION = "invert"
CATEGORY = "image" CATEGORY = "image/color"
def invert(self, image): def invert(self, image):
s = 1.0 - image s = 1.0 - image
@ -1903,7 +1903,7 @@ class ImageBatch:
RETURN_TYPES = ("IMAGE",) RETURN_TYPES = ("IMAGE",)
FUNCTION = "batch" FUNCTION = "batch"
CATEGORY = "image" CATEGORY = "image/batch"
DEPRECATED = True DEPRECATED = True
def batch(self, image1, image2): def batch(self, image1, image2):
@ -1960,7 +1960,7 @@ class ImagePadForOutpaint:
RETURN_TYPES = ("IMAGE", "MASK") RETURN_TYPES = ("IMAGE", "MASK")
FUNCTION = "expand_image" FUNCTION = "expand_image"
CATEGORY = "image" CATEGORY = "image/transform"
def expand_image(self, image, left, top, right, bottom, feathering): def expand_image(self, image, left, top, right, bottom, feathering):
d1, d2, d3, d4 = image.size() d1, d2, d3, d4 = image.size()
@ -2103,7 +2103,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ConditioningSetArea": "Conditioning (Set Area)", "ConditioningSetArea": "Conditioning (Set Area)",
"ConditioningSetAreaPercentage": "Conditioning (Set Area with Percentage)", "ConditioningSetAreaPercentage": "Conditioning (Set Area with Percentage)",
"ConditioningSetMask": "Conditioning (Set Mask)", "ConditioningSetMask": "Conditioning (Set Mask)",
"ControlNetApply": "Apply ControlNet (OLD)", "ControlNetApply": "Apply ControlNet (DEPRECATED)",
"ControlNetApplyAdvanced": "Apply ControlNet", "ControlNetApplyAdvanced": "Apply ControlNet",
# Latent # Latent
"VAEEncodeForInpaint": "VAE Encode (for Inpainting)", "VAEEncodeForInpaint": "VAE Encode (for Inpainting)",
@ -2121,6 +2121,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"LatentFromBatch" : "Latent From Batch", "LatentFromBatch" : "Latent From Batch",
"RepeatLatentBatch": "Repeat Latent Batch", "RepeatLatentBatch": "Repeat Latent Batch",
# Image # Image
"EmptyImage": "Empty Image",
"SaveImage": "Save Image", "SaveImage": "Save Image",
"PreviewImage": "Preview Image", "PreviewImage": "Preview Image",
"LoadImage": "Load Image", "LoadImage": "Load Image",
@ -2128,15 +2129,15 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"LoadImageOutput": "Load Image (from Outputs)", "LoadImageOutput": "Load Image (from Outputs)",
"ImageScale": "Upscale Image", "ImageScale": "Upscale Image",
"ImageScaleBy": "Upscale Image By", "ImageScaleBy": "Upscale Image By",
"ImageInvert": "Invert Image", "ImageInvert": "Invert Image Colors",
"ImagePadForOutpaint": "Pad Image for Outpainting", "ImagePadForOutpaint": "Pad Image for Outpainting",
"ImageBatch": "Batch Images", "ImageBatch": "Batch Images (DEPRECATED)",
"ImageCrop": "Image Crop", "ImageCrop": "Crop Image",
"ImageStitch": "Image Stitch", "ImageStitch": "Stitch Images",
"ImageBlend": "Image Blend", "ImageBlend": "Blend Images",
"ImageBlur": "Image Blur", "ImageBlur": "Blur Image",
"ImageQuantize": "Image Quantize", "ImageQuantize": "Quantize Image",
"ImageSharpen": "Image Sharpen", "ImageSharpen": "Sharpen Image",
"ImageScaleToTotalPixels": "Scale Image to Total Pixels", "ImageScaleToTotalPixels": "Scale Image to Total Pixels",
"GetImageSize": "Get Image Size", "GetImageSize": "Get Image Size",
# _for_testing # _for_testing