From 139718dec33eac51b22e636956c220bcd587d694 Mon Sep 17 00:00:00 2001 From: Sida Liu Date: Sat, 6 Apr 2024 17:36:33 +0800 Subject: [PATCH 1/4] init --- __init__.py | 88 ++++++++++++++++++++++++++++++++++++++++++++ js/string_control.js | 43 ++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 __init__.py create mode 100644 js/string_control.js diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..f5c1d24 --- /dev/null +++ b/__init__.py @@ -0,0 +1,88 @@ +class DebugNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + """ + Return a dictionary which contains config for all input fields. + Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". + Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. + The type can be a list for selection. + + Returns: `dict`: + - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` + - Value input_fields (`dict`): Contains input fields config: + * Key field_name (`string`): Name of a entry-point method's argument + * Value field_config (`tuple`): + + First value is a string indicate the type of field or a list for selection. + + Secound value is a config for type "INT", "STRING" or "FLOAT". + """ + return { + "required": { + "string_field": ("STRING", { + "multiline": True, #True if you want the field to look like the one on the ClipTextEncode node + "default": "Connect any of the inputs to get more information." + }), + }, + "optional": { + "MODEL": ("MODEL",), + "CLIP": ("CLIP",), + "VAE": ("VAE",), + "CONDITIONING": ("CONDITIONING",), + "LATENT": ("LATENT",), + }, + } + + RETURN_TYPES = () + FUNCTION = "watch" + OUTPUT_NODE = True + CATEGORY = "Debug" + + def size_to_human_readable(self, size_in_bytes): + if size_in_bytes < 1024: + return f"{size_in_bytes} B" + elif size_in_bytes < 1024**2: + return f"{size_in_bytes / 1024:.1f} KB" + elif size_in_bytes < 1024**3: + return f"{size_in_bytes / 1024**2:.1f} MB" + elif size_in_bytes < 1024**4: + return f"{size_in_bytes / 1024**3:.1f} GB" + else: + return f"{size_in_bytes / 1024**4:.1f} TB" + + def watch(self, string_field, MODEL=None, CLIP=None, VAE=None, CONDITIONING=None, LATENT=None): + results = "" + if MODEL: + results += "MODEL:\n" + results += "\tArch:\t" + MODEL.model.latent_format.__class__.__name__ + "\n" + results += f"\tSize:\t{self.size_to_human_readable(MODEL.size)}\n" + results += "\n" + + if CLIP: + results += "CLIP:\n\n" + results += str(type(CLIP.tokenizer)) + "\n" + # results += f"size: {CLIP.size // 1024 // 1024}MB\n" + results += "\n" + + if VAE: + results += "VAE:\n\n" + results += str(type(VAE.first_stage_model)) + "\n" + # results += f"size: {VAE.size // 1024 // 1024}MB\n" + results += "\n" + + return { "ui": { "string_field": results } } + +# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension +WEB_DIRECTORY = "./js" + +# A dictionary that contains all nodes you want to export with their names +# NOTE: names should be globally unique +NODE_CLASS_MAPPINGS = { + "DebugNode": DebugNode +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "DebugNode": "Debug" +} diff --git a/js/string_control.js b/js/string_control.js new file mode 100644 index 0000000..aa9c485 --- /dev/null +++ b/js/string_control.js @@ -0,0 +1,43 @@ +import { api } from '../../scripts/api.js'; +import { app } from '../../scripts/app.js'; // Assuming `app` exports the current application instance + +// Function to update STRING widget's content based on server response +function updateStringWidgetContent(nodeId, widgetName, newContent) { + const node = app.graph._nodes.find(n => n.id === nodeId); + if (!node) { + console.error('Node not found:', nodeId); + return; + } + + const widget = node.widgets.find(w => w.name === widgetName && w.type === 'customtext'); + if (!widget) { + console.error('STRING widget not found:', widgetName); + return; + } + + widget.value = newContent; + // Optionally, if there's a callback function defined for the widget, call it + if (widget.callback) { + widget.callback(newContent); + } +} + +// Listen for a specific event from the server indicating a script execution is completed +api.addEventListener('executed', event => { + const detail = event.detail; + + // Check if `output` exists and has a `string_field` + if (detail.output && Array.isArray(detail.output.string_field)) { + // Join the array elements into a string + const content = detail.output.string_field.join(''); + + // Example node ID and widget name, replace with actual ones + const nodeId = parseInt(app.runningNodeId); // Replace with actual node ID + const widgetName = 'string_field'; // Replace with actual widget name + + updateStringWidgetContent(nodeId, widgetName, content); + } else { + console.error('Unexpected detail format:', detail); + } + +}); From 3fdd04de167c9f4c8496fa91c01cdeacf0dd83ca Mon Sep 17 00:00:00 2001 From: Sida Liu Date: Sat, 6 Apr 2024 19:51:22 +0800 Subject: [PATCH 2/4] naming --- __init__.py | 108 ++++++++++++--------- js/debug_node.js | 6 ++ js/{string_control.js => string_output.js} | 8 +- 3 files changed, 73 insertions(+), 49 deletions(-) create mode 100644 js/debug_node.js rename js/{string_control.js => string_output.js} (83%) diff --git a/__init__.py b/__init__.py index f5c1d24..96e501b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,23 +1,21 @@ -class DebugNode: +def size_to_human_readable(size_in_bytes): + if size_in_bytes < 1024: + return f"{size_in_bytes} B" + elif size_in_bytes < 1024**2: + return f"{size_in_bytes / 1024:.1f} KB" + elif size_in_bytes < 1024**3: + return f"{size_in_bytes / 1024**2:.1f} MB" + elif size_in_bytes < 1024**4: + return f"{size_in_bytes / 1024**3:.1f} GB" + else: + return f"{size_in_bytes / 1024**4:.1f} TB" + +class DebugWatchNode: def __init__(self): pass @classmethod def INPUT_TYPES(s): - """ - Return a dictionary which contains config for all input fields. - Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". - Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. - The type can be a list for selection. - - Returns: `dict`: - - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` - - Value input_fields (`dict`): Contains input fields config: - * Key field_name (`string`): Name of a entry-point method's argument - * Value field_config (`tuple`): - + First value is a string indicate the type of field or a list for selection. - + Secound value is a config for type "INT", "STRING" or "FLOAT". - """ return { "required": { "string_field": ("STRING", { @@ -26,63 +24,83 @@ class DebugNode: }), }, "optional": { - "MODEL": ("MODEL",), - "CLIP": ("CLIP",), - "VAE": ("VAE",), - "CONDITIONING": ("CONDITIONING",), - "LATENT": ("LATENT",), + "model": ("MODEL",), + "clip": ("CLIP",), + "vae": ("VAE",), + "conditioning": ("CONDITIONING",), + "latent": ("LATENT",), }, } RETURN_TYPES = () FUNCTION = "watch" OUTPUT_NODE = True - CATEGORY = "Debug" + CATEGORY = "utils" - def size_to_human_readable(self, size_in_bytes): - if size_in_bytes < 1024: - return f"{size_in_bytes} B" - elif size_in_bytes < 1024**2: - return f"{size_in_bytes / 1024:.1f} KB" - elif size_in_bytes < 1024**3: - return f"{size_in_bytes / 1024**2:.1f} MB" - elif size_in_bytes < 1024**4: - return f"{size_in_bytes / 1024**3:.1f} GB" - else: - return f"{size_in_bytes / 1024**4:.1f} TB" - - def watch(self, string_field, MODEL=None, CLIP=None, VAE=None, CONDITIONING=None, LATENT=None): + def watch(self, string_field, model=None, clip=None, vae=None, CONDITIONING=None, LATENT=None): results = "" - if MODEL: + if model: results += "MODEL:\n" - results += "\tArch:\t" + MODEL.model.latent_format.__class__.__name__ + "\n" - results += f"\tSize:\t{self.size_to_human_readable(MODEL.size)}\n" + results += "\tArch:\t" + model.model.latent_format.__class__.__name__ + "\n" + results += f"\tSize:\t{size_to_human_readable(model.size)}\n" results += "\n" - if CLIP: + if clip: results += "CLIP:\n\n" - results += str(type(CLIP.tokenizer)) + "\n" - # results += f"size: {CLIP.size // 1024 // 1024}MB\n" + results += str(type(clip.tokenizer)) + "\n" results += "\n" - if VAE: + if vae: results += "VAE:\n\n" - results += str(type(VAE.first_stage_model)) + "\n" - # results += f"size: {VAE.size // 1024 // 1024}MB\n" + results += str(type(vae.first_stage_model)) + "\n" results += "\n" return { "ui": { "string_field": results } } +class DebugWatchModelNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "output": ("STRING", { + "multiline": True, #True if you want the field to look like the one on the ClipTextEncode node + "default": "Connect any of the inputs to get more information." + }), + }, + } + + RETURN_TYPES = () + FUNCTION = "watch" + OUTPUT_NODE = True + CATEGORY = "utils" + + def watch(self, output, model=None): + results = "" + if model: + results += "MODEL:\n" + results += "\tArch:\t" + model.model.latent_format.__class__.__name__ + "\n" + results += f"\tSize:\t{size_to_human_readable(model.size)}\n" + results += "\n" + + return { "ui": { "output": results } } + + # Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension WEB_DIRECTORY = "./js" # A dictionary that contains all nodes you want to export with their names # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { - "DebugNode": DebugNode + "DebugWatchNode": DebugWatchNode, + "DebugWatchModelNode": DebugWatchModelNode, } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { - "DebugNode": "Debug" + "DebugWatchNode": "Debug Watch", + "DebugWatchModelNode": "Debug Watch Model", } diff --git a/js/debug_node.js b/js/debug_node.js new file mode 100644 index 0000000..6840112 --- /dev/null +++ b/js/debug_node.js @@ -0,0 +1,6 @@ +import {app} from "../../scripts/app.js"; + +// // Node that add notes to your project +// app.registerExtension({ +// name: "Comfy.Debug", +// }); diff --git a/js/string_control.js b/js/string_output.js similarity index 83% rename from js/string_control.js rename to js/string_output.js index aa9c485..79b9806 100644 --- a/js/string_control.js +++ b/js/string_output.js @@ -26,14 +26,14 @@ function updateStringWidgetContent(nodeId, widgetName, newContent) { api.addEventListener('executed', event => { const detail = event.detail; - // Check if `output` exists and has a `string_field` - if (detail.output && Array.isArray(detail.output.string_field)) { + // Check if `output` exists and has a `output` + if (detail.output && Array.isArray(detail.output.output)) { // Join the array elements into a string - const content = detail.output.string_field.join(''); + const content = detail.output.output.join(''); // Example node ID and widget name, replace with actual ones const nodeId = parseInt(app.runningNodeId); // Replace with actual node ID - const widgetName = 'string_field'; // Replace with actual widget name + const widgetName = 'output'; // Replace with actual widget name updateStringWidgetContent(nodeId, widgetName, content); } else { From a6b0dbcb47a6c181187f3028d2ce2b319ca80aba Mon Sep 17 00:00:00 2001 From: Sida Liu Date: Sat, 6 Apr 2024 20:28:43 +0800 Subject: [PATCH 3/4] naming --- __init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/__init__.py b/__init__.py index 96e501b..a32cd61 100644 --- a/__init__.py +++ b/__init__.py @@ -10,7 +10,7 @@ def size_to_human_readable(size_in_bytes): else: return f"{size_in_bytes / 1024**4:.1f} TB" -class DebugWatchNode: +class DebugInspectorNode: def __init__(self): pass @@ -57,7 +57,7 @@ class DebugWatchNode: return { "ui": { "string_field": results } } -class DebugWatchModelNode: +class DebugModelInspectorNode: def __init__(self): pass @@ -95,12 +95,12 @@ WEB_DIRECTORY = "./js" # A dictionary that contains all nodes you want to export with their names # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { - "DebugWatchNode": DebugWatchNode, - "DebugWatchModelNode": DebugWatchModelNode, + "DebugInspectorNode": DebugInspectorNode, + "DebugModelInspectorNode": DebugModelInspectorNode, } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { - "DebugWatchNode": "Debug Watch", - "DebugWatchModelNode": "Debug Watch Model", + "DebugInspectorNode": "Debug: Inspector", + "DebugModelInspectorNode": "Debug: Model Inspector", } From 702b0afa66a2764a95e684c2ba4067c3952cba4e Mon Sep 17 00:00:00 2001 From: Sida Liu Date: Sun, 7 Apr 2024 19:18:27 +0800 Subject: [PATCH 4/4] two nodes for inspecting models --- __init__.py | 82 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/__init__.py b/__init__.py index a32cd61..7f829bd 100644 --- a/__init__.py +++ b/__init__.py @@ -1,14 +1,20 @@ -def size_to_human_readable(size_in_bytes): +import inspect +import io + +def size_to_human_readable(size_in_bytes, in_bytes=True): if size_in_bytes < 1024: - return f"{size_in_bytes} B" + return f"{size_in_bytes} " elif size_in_bytes < 1024**2: - return f"{size_in_bytes / 1024:.1f} KB" + return f"{size_in_bytes / 1024:.1f} K" elif size_in_bytes < 1024**3: - return f"{size_in_bytes / 1024**2:.1f} MB" + return f"{size_in_bytes / 1024**2:.1f} M" elif size_in_bytes < 1024**4: - return f"{size_in_bytes / 1024**3:.1f} GB" + if in_bytes: + return f"{size_in_bytes / 1024**3:.1f} G" + else: + return f"{size_in_bytes / 1024**3:.1f} B" else: - return f"{size_in_bytes / 1024**4:.1f} TB" + return f"{size_in_bytes / 1024**4:.1f} T" class DebugInspectorNode: def __init__(self): @@ -68,7 +74,7 @@ class DebugModelInspectorNode: "model": ("MODEL",), "output": ("STRING", { "multiline": True, #True if you want the field to look like the one on the ClipTextEncode node - "default": "Connect any of the inputs to get more information." + "default": "" }), }, } @@ -81,13 +87,61 @@ class DebugModelInspectorNode: def watch(self, output, model=None): results = "" if model: - results += "MODEL:\n" - results += "\tArch:\t" + model.model.latent_format.__class__.__name__ + "\n" - results += f"\tSize:\t{size_to_human_readable(model.size)}\n" + results += "Base:\t\t" + model.model.latent_format.__class__.__name__ + "\n" + results += f"Size:\t\t{size_to_human_readable(model.size, in_bytes=True)}B\n" + parameter_size = sum(p.numel() for p in model.model.parameters()) + results += f"Parameters:\t{size_to_human_readable(parameter_size, in_bytes=False)}\n" + + dtype_value = model.model.get_dtype() # Example: + dtype_str = str(dtype_value).split('.')[-1] # Split by '.' and take the last part + print(dtype_str) # This will print 'bfloat16' + results += f"dtype:\t\t{dtype_str}\n" + + results += f"Model Type:\t{model.model.model_type.name}\n" + results += "\n" return { "ui": { "output": results } } +class DebugModelPrintOutNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "output": ("STRING", { + "multiline": True, #True if you want the field to look like the one on the ClipTextEncode node + "default": "" + }), + }, + } + + RETURN_TYPES = () + FUNCTION = "watch" + OUTPUT_NODE = True + CATEGORY = "utils" + + def watch(self, output, model=None): + results = "" + # Check if the model object itself exists and has the attribute 'model' + if model and hasattr(model, 'model'): + # Check if the 'model' attribute of the model has the attribute 'diffusion_model' + if hasattr(model.model, 'diffusion_model'): + # Now we can safely print the diffusion_model + buffer = io.StringIO() + print(model.model.diffusion_model, file=buffer) + results += buffer.getvalue() + buffer.close() + else: + results += "No diffusion_model attribute found.\n" + else: + results += "Model is not defined or does not have a 'model' attribute.\n" + + return { "ui": { "output": results } } + # Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension WEB_DIRECTORY = "./js" @@ -95,12 +149,14 @@ WEB_DIRECTORY = "./js" # A dictionary that contains all nodes you want to export with their names # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { - "DebugInspectorNode": DebugInspectorNode, + # "DebugInspectorNode": DebugInspectorNode, "DebugModelInspectorNode": DebugModelInspectorNode, + "DebugModelPrintOutNode": DebugModelPrintOutNode, } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { - "DebugInspectorNode": "Debug: Inspector", - "DebugModelInspectorNode": "Debug: Model Inspector", + # "DebugInspectorNode": "Debug: Inspector", + "DebugModelInspectorNode": "Debug: Model Metrics", + "DebugModelPrintOutNode": "Debug: Model Architecture" }