From b07258cef2ec53e2f76ef9ae73682ca1aa08a9b1 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Tue, 18 Feb 2025 07:28:33 -0500 Subject: [PATCH 01/19] Fix typo. Let me know if this slows things down on 2000 series and below. --- comfy/model_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/model_management.py b/comfy/model_management.py index 9252afab..9ff63f35 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -1121,7 +1121,7 @@ def should_use_bf16(device=None, model_params=0, prioritize_performance=True, ma bf16_works = torch.cuda.is_bf16_supported() - if bf16_works or manual_cast: + if bf16_works and manual_cast: free_model_memory = maximum_vram_for_weights(device) if (not prioritize_performance) or model_params * 4 > free_model_memory: return True From acc152b674fd1c983acc6efd8aedbeb380660c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Sepp=C3=A4nen?= <40791699+kijai@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:06:54 +0200 Subject: [PATCH 02/19] Support loading and using SkyReels-V1-Hunyuan-I2V (#6862) * Support SkyReels-V1-Hunyuan-I2V * VAE scaling * Fix T2V oops * Proper latent scaling --- comfy/ldm/hunyuan_video/model.py | 2 +- comfy/model_base.py | 9 +++++++++ comfy/model_detection.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/comfy/ldm/hunyuan_video/model.py b/comfy/ldm/hunyuan_video/model.py index fc3a6744..f3f44584 100644 --- a/comfy/ldm/hunyuan_video/model.py +++ b/comfy/ldm/hunyuan_video/model.py @@ -310,7 +310,7 @@ class HunyuanVideo(nn.Module): shape[i] = shape[i] // self.patch_size[i] img = img.reshape([img.shape[0]] + shape + [self.out_channels] + self.patch_size) img = img.permute(0, 4, 1, 5, 2, 6, 3, 7) - img = img.reshape(initial_shape) + img = img.reshape(initial_shape[0], self.out_channels, initial_shape[2], initial_shape[3], initial_shape[4]) return img def forward(self, x, timestep, context, y, guidance=None, attention_mask=None, control=None, transformer_options={}, **kwargs): diff --git a/comfy/model_base.py b/comfy/model_base.py index 98f462b3..0eeaed79 100644 --- a/comfy/model_base.py +++ b/comfy/model_base.py @@ -871,6 +871,15 @@ class HunyuanVideo(BaseModel): if cross_attn is not None: out['c_crossattn'] = comfy.conds.CONDRegular(cross_attn) + image = kwargs.get("concat_latent_image", None) + noise = kwargs.get("noise", None) + + if image is not None: + padding_shape = (noise.shape[0], 16, noise.shape[2] - 1, noise.shape[3], noise.shape[4]) + latent_padding = torch.zeros(padding_shape, device=noise.device, dtype=noise.dtype) + image_latents = torch.cat([image.to(noise), latent_padding], dim=2) + out['c_concat'] = comfy.conds.CONDNoiseShape(self.process_latent_in(image_latents)) + guidance = kwargs.get("guidance", 6.0) if guidance is not None: out['guidance'] = comfy.conds.CONDRegular(torch.FloatTensor([guidance])) diff --git a/comfy/model_detection.py b/comfy/model_detection.py index 2644dd0d..5051f821 100644 --- a/comfy/model_detection.py +++ b/comfy/model_detection.py @@ -136,7 +136,7 @@ def detect_unet_config(state_dict, key_prefix): if '{}txt_in.individual_token_refiner.blocks.0.norm1.weight'.format(key_prefix) in state_dict_keys: #Hunyuan Video dit_config = {} dit_config["image_model"] = "hunyuan_video" - dit_config["in_channels"] = 16 + dit_config["in_channels"] = state_dict["img_in.proj.weight"].shape[1] #SkyReels img2video has 32 input channels dit_config["patch_size"] = [1, 2, 2] dit_config["out_channels"] = 16 dit_config["vec_in_dim"] = 768 From afc85cdeb64e1c758cd1d0fa8c99f0e3a9e9f9cd Mon Sep 17 00:00:00 2001 From: bymyself Date: Tue, 18 Feb 2025 15:53:01 -0700 Subject: [PATCH 03/19] Add Load Image Output node (#6790) * add LoadImageOutput node * add route for input/output/temp files * update node_typing.py * use literal type for image_folder field * mark node as beta --- api_server/routes/internal/internal_routes.py | 17 +++++++++- comfy/comfy_types/node_typing.py | 21 ++++++++++++ nodes.py | 32 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/api_server/routes/internal/internal_routes.py b/api_server/routes/internal/internal_routes.py index a66fe529..613b0f7c 100644 --- a/api_server/routes/internal/internal_routes.py +++ b/api_server/routes/internal/internal_routes.py @@ -1,8 +1,9 @@ from aiohttp import web from typing import Optional -from folder_paths import folder_names_and_paths +from folder_paths import folder_names_and_paths, get_directory_by_type from api_server.services.terminal_service import TerminalService import app.logger +import os class InternalRoutes: ''' @@ -50,6 +51,20 @@ class InternalRoutes: response[key] = folder_names_and_paths[key][0] return web.json_response(response) + @self.routes.get('/files/{directory_type}') + async def get_files(request: web.Request) -> web.Response: + directory_type = request.match_info['directory_type'] + if directory_type not in ("output", "input", "temp"): + return web.json_response({"error": "Invalid directory type"}, status=400) + + directory = get_directory_by_type(directory_type) + sorted_files = sorted( + (entry for entry in os.scandir(directory) if entry.is_file()), + key=lambda entry: -entry.stat().st_mtime + ) + return web.json_response([entry.name for entry in sorted_files], status=200) + + def get_app(self): if self._app is None: self._app = web.Application() diff --git a/comfy/comfy_types/node_typing.py b/comfy/comfy_types/node_typing.py index 056b1aa6..0f70fdb2 100644 --- a/comfy/comfy_types/node_typing.py +++ b/comfy/comfy_types/node_typing.py @@ -66,6 +66,19 @@ class IO(StrEnum): b = frozenset(value.split(",")) return not (b.issubset(a) or a.issubset(b)) +class RemoteInputOptions(TypedDict): + route: str + """The route to the remote source.""" + refresh_button: bool + """Specifies whether to show a refresh button in the UI below the widget.""" + control_after_refresh: Literal["first", "last"] + """Specifies the control after the refresh button is clicked. If "first", the first item will be automatically selected, and so on.""" + timeout: int + """The maximum amount of time to wait for a response from the remote source in milliseconds.""" + max_retries: int + """The maximum number of retries before aborting the request.""" + refresh: int + """The TTL of the remote input's value in milliseconds. Specifies the interval at which the remote input's value is refreshed.""" class InputTypeOptions(TypedDict): """Provides type hinting for the return type of the INPUT_TYPES node function. @@ -113,6 +126,14 @@ class InputTypeOptions(TypedDict): # defaultVal: str dynamicPrompts: bool """Causes the front-end to evaluate dynamic prompts (``STRING``)""" + # class InputTypeCombo(InputTypeOptions): + image_upload: bool + """Specifies whether the input should have an image upload button and image preview attached to it. Requires that the input's name is `image`.""" + image_folder: Literal["input", "output", "temp"] + """Specifies which folder to get preview images from if the input has the ``image_upload`` flag. + """ + remote: RemoteInputOptions + """Specifies the configuration for a remote input.""" class HiddenInputTypeDict(TypedDict): diff --git a/nodes.py b/nodes.py index 504a3376..b39adc65 100644 --- a/nodes.py +++ b/nodes.py @@ -1763,6 +1763,36 @@ class LoadImageMask: return True + +class LoadImageOutput(LoadImage): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("COMBO", { + "image_upload": True, + "image_folder": "output", + "remote": { + "route": "/internal/files/output", + "refresh_button": True, + "control_after_refresh": "first", + }, + }), + } + } + + DESCRIPTION = "Load an image from the output folder. When the refresh button is clicked, the node will update the image list and automatically select the first image, allowing for easy iteration." + EXPERIMENTAL = True + FUNCTION = "load_image_output" + + def load_image_output(self, image): + return self.load_image(f"{image} [output]") + + @classmethod + def VALIDATE_INPUTS(s, image): + return True + + class ImageScale: upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] crop_methods = ["disabled", "center"] @@ -1949,6 +1979,7 @@ NODE_CLASS_MAPPINGS = { "PreviewImage": PreviewImage, "LoadImage": LoadImage, "LoadImageMask": LoadImageMask, + "LoadImageOutput": LoadImageOutput, "ImageScale": ImageScale, "ImageScaleBy": ImageScaleBy, "ImageInvert": ImageInvert, @@ -2049,6 +2080,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "PreviewImage": "Preview Image", "LoadImage": "Load Image", "LoadImageMask": "Load Image (as Mask)", + "LoadImageOutput": "Load Image (from Outputs)", "ImageScale": "Upscale Image", "ImageScaleBy": "Upscale Image By", "ImageUpscaleWithModel": "Upscale Image (using Model)", From 0d4d9222c6922e123e81c9fea104275df33b497d Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Wed, 19 Feb 2025 07:11:49 -0500 Subject: [PATCH 04/19] Add early experimental SaveWEBM node to save .webm files. The frontend part isn't done yet so there is no video preview on the node or dragging the webm on the interface to load the workflow yet. This uses a new dependency: PyAV. --- comfy_extras/nodes_video.py | 75 +++++++++++++++++++++++++++++++++++++ nodes.py | 1 + requirements.txt | 1 + 3 files changed, 77 insertions(+) create mode 100644 comfy_extras/nodes_video.py diff --git a/comfy_extras/nodes_video.py b/comfy_extras/nodes_video.py new file mode 100644 index 00000000..f3922e03 --- /dev/null +++ b/comfy_extras/nodes_video.py @@ -0,0 +1,75 @@ +import os +import av +import torch +import folder_paths +import json +from fractions import Fraction + + +class SaveWEBM: + def __init__(self): + self.output_dir = folder_paths.get_output_directory() + self.type = "output" + self.prefix_append = "" + + @classmethod + def INPUT_TYPES(s): + return {"required": + {"images": ("IMAGE", ), + "filename_prefix": ("STRING", {"default": "ComfyUI"}), + "codec": (["vp9", "av1"],), + "fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}), + "crf": ("FLOAT", {"default": 32.0, "min": 0, "max": 63.0, "step": 1, "tooltip": "Higher crf means lower quality with a smaller file size, lower crf means higher quality higher filesize."}), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, + } + + RETURN_TYPES = () + FUNCTION = "save_images" + + OUTPUT_NODE = True + + CATEGORY = "image/video" + + EXPERIMENTAL = True + + def save_images(self, images, codec, fps, filename_prefix, crf, prompt=None, extra_pnginfo=None): + filename_prefix += self.prefix_append + full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) + + file = f"{filename}_{counter:05}_.webm" + container = av.open(os.path.join(full_output_folder, file), mode="w") + + if prompt is not None: + container.metadata["prompt"] = json.dumps(prompt) + + if extra_pnginfo is not None: + for x in extra_pnginfo: + container.metadata[x] = json.dumps(extra_pnginfo[x]) + + codec_map = {"vp9": "libvpx-vp9", "av1": "libaom-av1"} + stream = container.add_stream(codec_map[codec], rate=Fraction(round(fps * 1000), 1000)) + stream.width = images.shape[-2] + stream.height = images.shape[-3] + stream.pix_fmt = "yuv420p" + stream.bit_rate = 0 + stream.options = {'crf': str(crf)} + + for frame in images: + frame = av.VideoFrame.from_ndarray(torch.clamp(frame[..., :3] * 255, min=0, max=255).to(device=torch.device("cpu"), dtype=torch.uint8).numpy(), format="rgb24") + for packet in stream.encode(frame): + container.mux(packet) + container.close() + + results = [{ + "filename": file, + "subfolder": subfolder, + "type": self.type + }] + + return {"ui": {"images": results, "animated": (True,)}} # TODO: frontend side + + +NODE_CLASS_MAPPINGS = { + "SaveWEBM": SaveWEBM, +} diff --git a/nodes.py b/nodes.py index b39adc65..4e68af79 100644 --- a/nodes.py +++ b/nodes.py @@ -2265,6 +2265,7 @@ def init_builtin_extra_nodes(): "nodes_hooks.py", "nodes_load_3d.py", "nodes_cosmos.py", + "nodes_video.py", "nodes_lumina2.py", ] diff --git a/requirements.txt b/requirements.txt index 3bc945a1..3229fe81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ psutil kornia>=0.7.1 spandrel soundfile +av From 5715be2ca9930b7239590524203d89f74ec9f568 Mon Sep 17 00:00:00 2001 From: maedtb Date: Wed, 19 Feb 2025 07:14:45 -0500 Subject: [PATCH 05/19] Fix Hunyuan unet config detection for some models. (#6877) The change to support 32 channel hunyuan models is missing the `key_prefix` on the key. This addresses a complain in the comments of acc152b674fd1c983acc6efd8aedbeb380660c0c. --- comfy/model_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/model_detection.py b/comfy/model_detection.py index 5051f821..b1faa63f 100644 --- a/comfy/model_detection.py +++ b/comfy/model_detection.py @@ -136,7 +136,7 @@ def detect_unet_config(state_dict, key_prefix): if '{}txt_in.individual_token_refiner.blocks.0.norm1.weight'.format(key_prefix) in state_dict_keys: #Hunyuan Video dit_config = {} dit_config["image_model"] = "hunyuan_video" - dit_config["in_channels"] = state_dict["img_in.proj.weight"].shape[1] #SkyReels img2video has 32 input channels + dit_config["in_channels"] = state_dict['{}img_in.proj.weight'.format(key_prefix)].shape[1] #SkyReels img2video has 32 input channels dit_config["patch_size"] = [1, 2, 2] dit_config["out_channels"] = 16 dit_config["vec_in_dim"] = 768 From b4d3652d88927a341f22a35252471562f1f25f1b Mon Sep 17 00:00:00 2001 From: "Dr.Lt.Data" <128333288+ltdrdata@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:15:36 +0900 Subject: [PATCH 06/19] fixed: crash caused by outdated incompatible aiohttp dependency (#6841) https://github.com/comfyanonymous/ComfyUI/issues/6038#issuecomment-2661776795 https://github.com/comfyanonymous/ComfyUI/issues/5814#issue-2700816845 --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3229fe81..afbcb7cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,8 @@ transformers>=4.28.1 tokenizers>=0.13.3 sentencepiece safetensors>=0.4.2 -aiohttp +aiohttp>=3.11.8 +yarl>=1.18.0 pyyaml Pillow scipy From c5be423d6bf0e53d460471f49d75455e621773c0 Mon Sep 17 00:00:00 2001 From: Silver <65376327+silveroxides@users.noreply.github.com> Date: Thu, 20 Feb 2025 13:07:07 +0100 Subject: [PATCH 07/19] Fix link pointing to non-exisiting docs (#6891) * Fix link pointing to non-exisiting docs The current link is pointing to a path that does not exist any longer. I changed it to point to the currect correct path for custom nodes datatypes. * Update node_typing.py --- comfy/comfy_types/node_typing.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/comfy/comfy_types/node_typing.py b/comfy/comfy_types/node_typing.py index 0f70fdb2..0696dbe5 100644 --- a/comfy/comfy_types/node_typing.py +++ b/comfy/comfy_types/node_typing.py @@ -85,7 +85,7 @@ class InputTypeOptions(TypedDict): Due to IDE limitations with unions, for now all options are available for all types (e.g. `label_on` is hinted even when the type is not `IO.BOOLEAN`). - Comfy Docs: https://docs.comfy.org/essentials/custom_node_datatypes + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/datatypes """ default: bool | str | float | int | list | tuple @@ -154,7 +154,7 @@ class HiddenInputTypeDict(TypedDict): class InputTypeDict(TypedDict): """Provides type hinting for node INPUT_TYPES. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_more_on_inputs + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/more_on_inputs """ required: dict[str, tuple[IO, InputTypeOptions]] @@ -164,14 +164,14 @@ class InputTypeDict(TypedDict): hidden: HiddenInputTypeDict """Offers advanced functionality and server-client communication. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_more_on_inputs#hidden-inputs + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/more_on_inputs#hidden-inputs """ class ComfyNodeABC(ABC): """Abstract base class for Comfy nodes. Includes the names and expected types of attributes. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview """ DESCRIPTION: str @@ -188,7 +188,7 @@ class ComfyNodeABC(ABC): CATEGORY: str """The category of the node, as per the "Add Node" menu. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#category + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#category """ EXPERIMENTAL: bool """Flags a node as experimental, informing users that it may change or not work as expected.""" @@ -202,9 +202,9 @@ class ComfyNodeABC(ABC): * Must include the ``required`` key, which describes all inputs that must be connected for the node to execute. * The ``optional`` key can be added to describe inputs which do not need to be connected. - * The ``hidden`` key offers some advanced functionality. More info at: https://docs.comfy.org/essentials/custom_node_more_on_inputs#hidden-inputs + * The ``hidden`` key offers some advanced functionality. More info at: https://docs.comfy.org/custom-nodes/backend/more_on_inputs#hidden-inputs - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#input-types + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#input-types """ return {"required": {}} @@ -219,7 +219,7 @@ class ComfyNodeABC(ABC): By default, a node is not considered an output. Set ``OUTPUT_NODE = True`` to specify that it is. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#output-node + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#output-node """ INPUT_IS_LIST: bool """A flag indicating if this node implements the additional code necessary to deal with OUTPUT_IS_LIST nodes. @@ -230,7 +230,7 @@ class ComfyNodeABC(ABC): A node can also override the default input behaviour and receive the whole list in a single call. This is done by setting a class attribute `INPUT_IS_LIST` to ``True``. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_lists#list-processing + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/lists#list-processing """ OUTPUT_IS_LIST: tuple[bool] """A tuple indicating which node outputs are lists, but will be connected to nodes that expect individual items. @@ -248,7 +248,7 @@ class ComfyNodeABC(ABC): the node should provide a class attribute `OUTPUT_IS_LIST`, which is a ``tuple[bool]``, of the same length as `RETURN_TYPES`, specifying which outputs which should be so treated. - Comfy Docs: https://docs.comfy.org/essentials/custom_node_lists#list-processing + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/lists#list-processing """ RETURN_TYPES: tuple[IO] @@ -258,19 +258,19 @@ class ComfyNodeABC(ABC): RETURN_TYPES = (IO.INT, "INT", "CUSTOM_TYPE") - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#return-types + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#return-types """ RETURN_NAMES: tuple[str] """The output slot names for each item in `RETURN_TYPES`, e.g. ``RETURN_NAMES = ("count", "filter_string")`` - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#return-names + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#return-names """ OUTPUT_TOOLTIPS: tuple[str] """A tuple of strings to use as tooltips for node outputs, one for each item in `RETURN_TYPES`.""" FUNCTION: str """The name of the function to execute as a literal string, e.g. `FUNCTION = "execute"` - Comfy Docs: https://docs.comfy.org/essentials/custom_node_server_overview#function + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#function """ @@ -288,7 +288,7 @@ class CheckLazyMixin: Params should match the nodes execution ``FUNCTION`` (self, and all inputs by name). Will be executed repeatedly until it returns an empty list, or all requested items were already evaluated (and sent as params). - Comfy Docs: https://docs.comfy.org/essentials/custom_node_lazy_evaluation#defining-check-lazy-status + Comfy Docs: https://docs.comfy.org/custom-nodes/backend/lazy_evaluation#defining-check-lazy-status """ need = [name for name in kwargs if kwargs[name] is None] From 29d4384a75abb4ca90f5b64e70499f2120d76a3a Mon Sep 17 00:00:00 2001 From: Robin Huang Date: Thu, 20 Feb 2025 04:09:45 -0800 Subject: [PATCH 08/19] Normalize extra_model_config.yaml paths to prevent duplicates. (#6885) * Normalize extra_model_config.yaml paths before adding. * Fix tests. * Fix tests. --- tests-unit/utils/extra_config_test.py | 6 +++--- utils/extra_config.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests-unit/utils/extra_config_test.py b/tests-unit/utils/extra_config_test.py index 6d232079..eae1aa3d 100644 --- a/tests-unit/utils/extra_config_test.py +++ b/tests-unit/utils/extra_config_test.py @@ -145,7 +145,7 @@ def test_load_extra_model_paths_expands_appdata( else: expected_base_path = '/Users/TestUser/AppData/Roaming/ComfyUI' expected_calls = [ - ('checkpoints', os.path.join(expected_base_path, 'models/checkpoints'), False), + ('checkpoints', os.path.normpath(os.path.join(expected_base_path, 'models/checkpoints')), False), ] assert mock_add_model_folder_path.call_count == len(expected_calls) @@ -197,8 +197,8 @@ def test_load_extra_path_config_relative_base_path( load_extra_path_config(dummy_yaml_name) - expected_checkpoints = os.path.abspath(os.path.join(str(tmp_path), sub_folder, "checkpoints")) - expected_some_value = os.path.abspath(os.path.join(str(tmp_path), sub_folder, "some_value")) + expected_checkpoints = os.path.abspath(os.path.join(str(tmp_path), "my_rel_base", "checkpoints")) + expected_some_value = os.path.abspath(os.path.join(str(tmp_path), "my_rel_base", "some_value")) actual_paths = folder_paths.folder_names_and_paths["checkpoints"][0] assert len(actual_paths) == 1, "Should have one path added for 'checkpoints'." diff --git a/utils/extra_config.py b/utils/extra_config.py index b7196e36..a0fcda9e 100644 --- a/utils/extra_config.py +++ b/utils/extra_config.py @@ -29,5 +29,6 @@ def load_extra_path_config(yaml_path): full_path = os.path.join(base_path, full_path) elif not os.path.isabs(full_path): full_path = os.path.abspath(os.path.join(yaml_dir, y)) - logging.info("Adding extra search path {} {}".format(x, full_path)) - folder_paths.add_model_folder_path(x, full_path, is_default) + normalized_path = os.path.normpath(full_path) + logging.info("Adding extra search path {} {}".format(x, normalized_path)) + folder_paths.add_model_folder_path(x, normalized_path, is_default) From 12da6ef581cd9f47a4341d74159ce32f3d2f3e8d Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Thu, 20 Feb 2025 09:29:59 -0500 Subject: [PATCH 09/19] Apparently directml supports fp16. --- comfy/model_management.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/comfy/model_management.py b/comfy/model_management.py index 9ff63f35..9066e0dc 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -1021,8 +1021,6 @@ def is_directml_enabled(): return False def should_use_fp16(device=None, model_params=0, prioritize_performance=True, manual_cast=False): - global directml_enabled - if device is not None: if is_device_cpu(device): return False @@ -1033,8 +1031,8 @@ def should_use_fp16(device=None, model_params=0, prioritize_performance=True, ma if FORCE_FP32: return False - if directml_enabled: - return False + if is_directml_enabled(): + return True if (device is not None and is_device_mps(device)) or mps_mode(): return True From d37272532cf80fe7c58532b4161502fa9043ed33 Mon Sep 17 00:00:00 2001 From: Robin Huang Date: Thu, 20 Feb 2025 15:26:16 -0800 Subject: [PATCH 10/19] Add discord channel to support section. (#6900) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 44f46a41..e50ed660 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,8 @@ Use `--tls-keyfile key.pem --tls-certfile cert.pem` to enable TLS/SSL, the app w ## Support and dev channel +[Discord](https://comfy.org/discord): Try the #help or #feedback channels. + [Matrix space: #comfyui_space:matrix.org](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) (it's like discord but open source). See also: [https://www.comfy.org/](https://www.comfy.org/) From f579a740ddcfa90a1dfdfd9a8378c27fa9b2ac87 Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Fri, 21 Feb 2025 21:58:12 +1100 Subject: [PATCH 11/19] Update frontend release schedule in README. (#6908) Changes release schedule from weekly to fortnightly. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e50ed660..83d67cef 100644 --- a/README.md +++ b/README.md @@ -311,7 +311,7 @@ For any bugs, issues, or feature requests related to the frontend, please use th The new frontend is now the default for ComfyUI. However, please note: -1. The frontend in the main ComfyUI repository is updated weekly. +1. The frontend in the main ComfyUI repository is updated fortnightly. 2. Daily releases are available in the separate frontend repository. To use the most up-to-date frontend version: @@ -328,7 +328,7 @@ To use the most up-to-date frontend version: --front-end-version Comfy-Org/ComfyUI_frontend@1.2.2 ``` -This approach allows you to easily switch between the stable weekly release and the cutting-edge daily updates, or even specific versions for testing purposes. +This approach allows you to easily switch between the stable fortnightly release and the cutting-edge daily updates, or even specific versions for testing purposes. ### Accessing the Legacy Frontend From 41c30e92e7c468dde630714a27431299de438490 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 21 Feb 2025 06:32:11 -0500 Subject: [PATCH 12/19] Let all model memory be offloaded on nvidia. --- comfy/model_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/model_management.py b/comfy/model_management.py index 9066e0dc..331aa9fd 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -220,7 +220,7 @@ def is_amd(): MIN_WEIGHT_MEMORY_RATIO = 0.4 if is_nvidia(): - MIN_WEIGHT_MEMORY_RATIO = 0.1 + MIN_WEIGHT_MEMORY_RATIO = 0.0 ENABLE_PYTORCH_ATTENTION = False if args.use_pytorch_cross_attention: From a6deca6d9ae36ec60722d66ad5c31cbb05725383 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 21 Feb 2025 20:14:30 -0500 Subject: [PATCH 13/19] Latest mac still has the black image bug. --- comfy/model_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/model_management.py b/comfy/model_management.py index 331aa9fd..86b4727a 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -943,7 +943,7 @@ def force_upcast_attention_dtype(): upcast = args.force_upcast_attention macos_version = mac_version() - if macos_version is not None and ((14, 5) <= macos_version <= (15, 2)): # black image bug on recent versions of macOS + if macos_version is not None and ((14, 5) <= macos_version <= (15, 3)): # black image bug on recent versions of macOS upcast = True if upcast: From 072db3bea6e530ad14c168a94cb83024358ecb9b Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 21 Feb 2025 20:24:07 -0500 Subject: [PATCH 14/19] Assume the mac black image bug won't be fixed before v16. --- comfy/model_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/model_management.py b/comfy/model_management.py index 86b4727a..8b6c4a66 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -943,7 +943,7 @@ def force_upcast_attention_dtype(): upcast = args.force_upcast_attention macos_version = mac_version() - if macos_version is not None and ((14, 5) <= macos_version <= (15, 3)): # black image bug on recent versions of macOS + if macos_version is not None and ((14, 5) <= macos_version < (16,)): # black image bug on recent versions of macOS upcast = True if upcast: From b50ab153f96fd396ea26a76529f164c5ae3b50a6 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 21 Feb 2025 20:28:28 -0500 Subject: [PATCH 15/19] Bump ComfyUI version to v0.3.15 --- comfyui_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/comfyui_version.py b/comfyui_version.py index 6be6f59f..fc7bb1df 100644 --- a/comfyui_version.py +++ b/comfyui_version.py @@ -1,3 +1,3 @@ # This file is automatically generated by the build process when version is # updated in pyproject.toml. -__version__ = "0.3.14" +__version__ = "0.3.15" diff --git a/pyproject.toml b/pyproject.toml index a450b9b0..877ae4d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ComfyUI" -version = "0.3.14" +version = "0.3.15" readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.9" From aff16532d4505d3df2129802f89309ec6eb4499a Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sat, 22 Feb 2025 04:45:14 -0500 Subject: [PATCH 16/19] Remove some useless code. --- comfy/ldm/modules/attention.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/comfy/ldm/modules/attention.py b/comfy/ldm/modules/attention.py index 975faa21..24fb9d95 100644 --- a/comfy/ldm/modules/attention.py +++ b/comfy/ldm/modules/attention.py @@ -41,27 +41,12 @@ def exists(val): return val is not None -def uniq(arr): - return{el: True for el in arr}.keys() - - def default(val, d): if exists(val): return val return d -def max_neg_value(t): - return -torch.finfo(t.dtype).max - - -def init_(tensor): - dim = tensor.shape[-1] - std = 1 / math.sqrt(dim) - tensor.uniform_(-std, std) - return tensor - - # feedforward class GEGLU(nn.Module): def __init__(self, dim_in, dim_out, dtype=None, device=None, operations=ops): From ace899e71a3d8d75f64a016fa398b95fa83e6978 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sun, 23 Feb 2025 04:45:54 -0500 Subject: [PATCH 17/19] Prioritize fp16 compute when using allow_fp16_accumulation --- comfy/model_management.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/comfy/model_management.py b/comfy/model_management.py index 8b6c4a66..f4a63c6d 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -256,9 +256,12 @@ if ENABLE_PYTORCH_ATTENTION: torch.backends.cuda.enable_flash_sdp(True) torch.backends.cuda.enable_mem_efficient_sdp(True) + +PRIORITIZE_FP16 = False # TODO: remove and replace with something that shows exactly which dtype is faster than the other try: if is_nvidia() and args.fast: torch.backends.cuda.matmul.allow_fp16_accumulation = True + PRIORITIZE_FP16 = True # TODO: limit to cards where it actually boosts performance except: pass @@ -681,6 +684,10 @@ def unet_dtype(device=None, model_params=0, supported_dtypes=[torch.float16, tor if model_params * 2 > free_model_memory: return fp8_dtype + if PRIORITIZE_FP16: + if torch.float16 in supported_dtypes and should_use_fp16(device=device, model_params=model_params): + return torch.float16 + for dt in supported_dtypes: if dt == torch.float16 and should_use_fp16(device=device, model_params=model_params): if torch.float16 in supported_dtypes: From 4553891bbd993d0ee37377a6a30e13bd0e070143 Mon Sep 17 00:00:00 2001 From: Robin Huang Date: Sun, 23 Feb 2025 16:13:39 -0800 Subject: [PATCH 18/19] Update installation documentation to include desktop + cli. (#6899) * Update installation documentation. * Add portable to description. * Move cli further down. --- README.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 83d67cef..b51f7a06 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,24 @@ ![ComfyUI Screenshot](https://github.com/user-attachments/assets/7ccaf2c1-9b72-41ae-9a89-5688c94b7abe) -This ui will let you design and execute advanced stable diffusion pipelines using a graph/nodes/flowchart based interface. For some workflow examples and see what ComfyUI can do you can check out: -### [ComfyUI Examples](https://comfyanonymous.github.io/ComfyUI_examples/) +ComfyUI lets you design and execute advanced stable diffusion pipelines using a graph/nodes/flowchart based interface. Available on Windows, Linux, and macOS. + +## Get Started + +#### [Desktop Application](https://www.comfy.org/download) +- The easiest way to get started. +- Available on Windows & macOS. + +#### [Windows Portable Package](#installing) +- Get the latest commits and completely portable. +- Available on Windows. + +#### [Manual Install](#manual-install-windows-linux) +Supports all operating systems and GPU types (NVIDIA, AMD, Intel, Apple Silicon, Ascend). + +## Examples +See what ComfyUI can do with the [example workflows](https://comfyanonymous.github.io/ComfyUI_examples/). -### [Installing ComfyUI](#installing) ## Features - Nodes/graph/flowchart interface to experiment and create complex Stable Diffusion workflows without needing to code anything. @@ -121,7 +135,7 @@ Workflow examples can be found on the [Examples page](https://comfyanonymous.git # Installing -## Windows +## Windows Portable There is a portable standalone build for Windows that should work for running on Nvidia GPUs or for running on your CPU only on the [releases page](https://github.com/comfyanonymous/ComfyUI/releases). @@ -141,6 +155,15 @@ See the [Config file](extra_model_paths.yaml.example) to set the search paths fo To run it on services like paperspace, kaggle or colab you can use my [Jupyter Notebook](notebooks/comfyui_colab.ipynb) + +## [comfy-cli](https://docs.comfy.org/comfy-cli/getting-started) + +You can install and start ComfyUI using comfy-cli: +```bash +pip install comfy-cli +comfy install +``` + ## Manual Install (Windows, Linux) python 3.13 is supported but using 3.12 is recommended because some custom nodes and their dependencies might not support it yet. From 96d891cb94d90f220e066cebad349887137f07a6 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Mon, 24 Feb 2025 05:41:07 -0500 Subject: [PATCH 19/19] Speedup on some models by not upcasting bfloat16 to float32 on mac. --- comfy/ldm/modules/attention.py | 13 +++++++------ comfy/model_management.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/comfy/ldm/modules/attention.py b/comfy/ldm/modules/attention.py index 24fb9d95..2758f950 100644 --- a/comfy/ldm/modules/attention.py +++ b/comfy/ldm/modules/attention.py @@ -30,11 +30,12 @@ ops = comfy.ops.disable_weight_init FORCE_UPCAST_ATTENTION_DTYPE = model_management.force_upcast_attention_dtype() -def get_attn_precision(attn_precision): +def get_attn_precision(attn_precision, current_dtype): if args.dont_upcast_attention: return None - if FORCE_UPCAST_ATTENTION_DTYPE is not None: - return FORCE_UPCAST_ATTENTION_DTYPE + + if FORCE_UPCAST_ATTENTION_DTYPE is not None and current_dtype in FORCE_UPCAST_ATTENTION_DTYPE: + return FORCE_UPCAST_ATTENTION_DTYPE[current_dtype] return attn_precision def exists(val): @@ -81,7 +82,7 @@ def Normalize(in_channels, dtype=None, device=None): return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True, dtype=dtype, device=device) def attention_basic(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False): - attn_precision = get_attn_precision(attn_precision) + attn_precision = get_attn_precision(attn_precision, q.dtype) if skip_reshape: b, _, _, dim_head = q.shape @@ -150,7 +151,7 @@ def attention_basic(q, k, v, heads, mask=None, attn_precision=None, skip_reshape def attention_sub_quad(query, key, value, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False): - attn_precision = get_attn_precision(attn_precision) + attn_precision = get_attn_precision(attn_precision, query.dtype) if skip_reshape: b, _, _, dim_head = query.shape @@ -220,7 +221,7 @@ def attention_sub_quad(query, key, value, heads, mask=None, attn_precision=None, return hidden_states def attention_split(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False): - attn_precision = get_attn_precision(attn_precision) + attn_precision = get_attn_precision(attn_precision, q.dtype) if skip_reshape: b, _, _, dim_head = q.shape diff --git a/comfy/model_management.py b/comfy/model_management.py index f4a63c6d..1e6599be 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -954,7 +954,7 @@ def force_upcast_attention_dtype(): upcast = True if upcast: - return torch.float32 + return {torch.float16: torch.float32} else: return None