From 23fa2ad485d066ae249dc808a10728d679a65d72 Mon Sep 17 00:00:00 2001 From: silveroxides Date: Thu, 13 Aug 2026 07:26:37 +0200 Subject: [PATCH] docs(nodes): add schema tooltips --- docs/consensus_mergers.md | 138 ++++++++++++ nodes/consensus_merger.py | 181 +++++++++++++--- nodes/dora_extract_wd.py | 8 +- nodes/dora_learned_wd.py | 30 +-- nodes/downloader_nodes.py | 14 +- nodes/lora_extract_svd.py | 6 +- nodes/lora_rename.py | 3 +- nodes/lora_resize.py | 30 +-- nodes/merger.py | 370 ++++++++++++++++----------------- nodes/metakeys.py | 5 + nodes/model_analysis.py | 39 +++- nodes/model_info_nodes.py | 24 +-- nodes/prunekeys.py | 37 ++-- nodes/renamekeys.py | 37 ++-- nodes/text_encoder_extract.py | 68 +++--- tests/test_consensus_merger.py | 2 + tests/test_model_analysis.py | 2 + 17 files changed, 654 insertions(+), 340 deletions(-) diff --git a/docs/consensus_mergers.md b/docs/consensus_mergers.md index 57a9c27..87d26b2 100644 --- a/docs/consensus_mergers.md +++ b/docs/consensus_mergers.md @@ -5,6 +5,12 @@ Consensus-Weighted Blending. They calculate an element-wise mean or median consensus, compare each aligned source vector to that consensus, and normalize similarity-derived weights before producing the output vector. +The broad mathematical and integration reference is maintained in +[`references/Spec-CWB`](../references/Spec-CWB/README.md). This document +describes the behavior actually exposed by this repository's offline merger +nodes. The specification covers additional contexts, including online +conditioning and spatial fusion, which are not controls of these nodes. + ## Node families Two-input and three-input variants are provided for checkpoints, standalone @@ -37,6 +43,138 @@ Inputs have equal prior weight. The similarity calculation supplies the effective contribution for each aligned vector. If every candidate is rejected or receives zero weight, CWB falls back to equal weighting. +### Weighting sequence + +For each aligned vector group, the merger performs these operations in order: + +1. Compute an element-wise mean or median consensus. +2. Measure each vector's cosine similarity to that consensus. +3. Reject similarities below `similarity_threshold`. +4. Optionally remap unequal accepted similarities with Dynamic Similarity + Contrast. +5. Apply `similarity^power_alpha` and, when enabled, the diversity bandpass. +6. Normalize the resulting contributor weights and calculate their weighted + sum. If no usable weight remains, use equal contributor weights. +7. Optionally apply `rescale_norm`. +8. Apply `global_scale`. + +### Complete control reference + +- **Execution Mode**: `MERGE` writes an output. `DOCUMENTATION ONLY` returns + this document without opening model inputs. +- **Model A**: primary contributor and preservation anchor. It supplies output + metadata and anchors shared names and shapes. The output key set remains the + union of all inputs. +- **Model B / Model C**: additional equal-prior contributors. CWB derives their + effective per-vector influence from consensus similarity; these nodes do not + expose model-level weights. +- **CWB Preset**: `custom` makes all manual CWB controls authoritative. A named + preset sets consensus, similarity cutoff, alpha/beta, norm rescaling, and its + DSC fields. Named presets force similarity alignment. Only `power_blend` + supplies an alignment threshold (0.9); other presets retain the manual + alignment threshold. `position_weight` and `preserve_common_prefix` remain + explicit extensions. A manually selected `global_scale` other than 1.0 also + remains active. +- **Consensus Type**: `mean` uses the arithmetic center; `median` uses the + element-wise median and is less sensitive to coordinate outliers. +- **Alignment Method**: `index` pairs first-axis vectors by position; + `similarity` uses greedy one-to-one cosine matching. Row reordering is + enabled only for embeddings and paired LoRA rank components. Ordinary + checkpoint, diffusion-model, and text-encoder tensors retain fixed + coordinates. +- **Alignment Threshold**: minimum original cosine score for accepting a + greedy row match. It does not filter already-paired contributors during CWB + weighting; that is `similarity_threshold`. +- **Similarity Threshold**: minimum cosine similarity between a participating + vector and the group consensus. Rejected vectors receive zero weight. If all + are rejected, the implementation falls back to equal weights. +- **Power Alpha**: exponent applied to accepted non-negative similarities. + Values above 1 increasingly favor contributors closest to consensus. Zero + makes every accepted contributor's similarity term equal to one. +- **Diversity Beta**: exponent for the diversity bandpass. Values above zero + suppress vectors extremely close to consensus relative to more varied + accepted vectors. Zero disables this term. +- **Rescale Norm**: after blending, preserve the merged direction but replace + its L2 magnitude with the mean L2 magnitude of the participating vectors: + + `merged = merged / ||merged|| × mean(||source_i||)` + + A zero merged vector remains unchanged. This is performed per aligned row, + not once per complete tensor. For LoRA, it is performed separately for each + paired A row and B column, so enabling it can materially change the effective + low-rank delta magnitude. +- **Global Scale**: multiplies the merged result after CWB. For LoRA it is + applied once through B/up so the represented delta is scaled once rather + than once per factor. +- **Dynamic Similarity Contrast**: when consensus similarities differ, remaps + them into the range 0.7–1.0 before alpha/beta weighting. It does not alter + row-alignment scores. +- **Soft Comfort Bandpass**: when beta is positive, uses + `(1.5 - similarity)^beta` instead of the narrower + `(1.001 - similarity)^beta`. It has no effect when beta is zero. +- **Position Weight**: blends normalized positional affinity into greedy row + selection. Zero uses cosine only and one uses positional affinity only. It + affects only similarity-aligned embeddings and LoRA rank components; the + original cosine score is still checked against the alignment threshold. +- **Preserve Common Prefix**: copies Model A's longest numerically identical + leading first-axis span before CWB. The current implementation applies this + to all tensors with at least two dimensions, including fixed-coordinate + model tensors. +- **Mismatch Mode**: for anchored tensors that are missing or incompatible, + `skip` preserves the anchor, `zeros` supplies a compatible zero contribution + where possible, and `error` aborts. A valid tensor supplied by only one + secondary input is copied unchanged. +- **Output Filename**: filename without extension, written atomically beneath + the matching category in ComfyUI's models directory. +- **Save Dtype**: requested dtype for generated floating tensors. Unless + Override Dtype is enabled, any participating FP32 source keeps the generated + logical result FP32. +- **Process Device**: device for per-layer FP32 arithmetic. A CUDA OOM releases + the failed working set and retries only that layer on CPU. +- **Exclude Patterns**: matching layers are preserved from their anchor instead + of merged. +- **Discard Patterns**: matching tensors or logical LoRA groups are absent from + the output. +- **Glob Patterns**: switches exclude/discard syntax from regular expressions + to shell-style globs. Pattern fields accept one entry per line. +- **Lazy Load**: enables UEL low-memory loading so tensors are streamed by work + unit and explicitly released. +- **Force Clear Cache**: runs garbage collection and clears the CUDA allocator + cache before each layer. This can reduce retained memory at a substantial + speed cost. +- **Override Dtype**: forces generated floating tensors to Save Dtype. Guarded + low-bit tensors and enabled 1D direct differences are exempt. +- **Include 1D Diffs** (LoRA nodes): enables CWB merging of recognized 1D + direct-difference tensors in FP32. When disabled, shared tensors preserve + Model A and secondary-only tensors preserve their earliest provider. + +Scalars and 1D generic tensors use direct mean/median handling. Alignment, +similarity weighting, alpha/beta, DSC, and norm rescaling apply to vector groups +in tensors with at least two dimensions, not to those scalar/1D paths. + +### Presets implemented by these nodes + +All named file-merger presets use a neutral `global_scale` of 1.0. This is an +intentional repository safety difference from the broad specification's 0.7 +calibration for some conditioning-oriented presets: persisted model and LoRA +strength is not reduced unless the user explicitly changes Global Scale. + +| Preset | Consensus | Alpha | Similarity cutoff | Beta | Norm rescale | DSC | Soft comfort | Alignment cutoff | +| --- | --- | ---: | ---: | ---: | --- | --- | --- | --- | +| `baseline` | median | 2.0 | 0.0 | 0.0 | no | no | no | manual value | +| `power_blend` | median | 8.0 | 0.75 | 0.0 | yes | yes | no | 0.9 | +| `high_clarity` | median | 3.0 | 0.3 | 0.0 | no | no | no | manual value | +| `smooth` | mean | 1.5 | 0.0 | 0.0 | no | no | no | manual value | +| `varied_merge` | median | 2.0 | 0.0 | 0.0 | yes | no | no | manual value | +| `diverse_concept` | median | 2.0 | 0.0 | 1.0 | yes | no | no | manual value | +| `high_diversity_concept` | median | 2.0 | 0.0 | 2.0 | yes | no | no | manual value | +| `dsc_baseline` | median | 2.0 | 0.0 | 0.0 | no | yes | yes | manual value | +| `dsc_high_clarity` | median | 4.0 | 0.3 | 0.0 | no | yes | yes | manual value | +| `dsc_smooth` | mean | 1.0 | 0.0 | 0.0 | no | yes | yes | manual value | +| `dsc_varied_merge` | median | 2.5 | 0.0 | 0.0 | yes | yes | yes | manual value | +| `dsc_diverse_concept` | median | 2.0 | 0.0 | 1.5 | yes | yes | yes | manual value | +| `dsc_high_diversity_concept` | median | 2.0 | 0.0 | 3.0 | yes | yes | yes | manual value | + ## Missing tensors and filtering - `skip` preserves Model A when an anchored input is missing or incompatible. diff --git a/nodes/consensus_merger.py b/nodes/consensus_merger.py index 759e7f3..5251c55 100644 --- a/nodes/consensus_merger.py +++ b/nodes/consensus_merger.py @@ -1324,35 +1324,163 @@ class ConsensusMergerLogic: def _common_inputs(model_type: str, input_count: int, default_filename: str): label = model_type inputs = [ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=folder_paths.get_filename_list(label)), - io.Combo.Input("model_b", options=folder_paths.get_filename_list(label)), + io.Combo.Input( + "execution_mode", + options=["MERGE", "DOCUMENTATION ONLY"], + tooltip="MERGE writes a new safetensors file. DOCUMENTATION ONLY returns the CWB reference without loading or merging inputs.", + ), + io.Combo.Input( + "model_a", + options=folder_paths.get_filename_list(label), + tooltip="Primary contributor and preservation anchor. Supplies output metadata and anchors shared tensor names and shapes.", + ), + io.Combo.Input( + "model_b", + options=folder_paths.get_filename_list(label), + tooltip="Second equal-prior contributor. CWB derives its effective per-vector influence from consensus similarity.", + ), ] if input_count == 3: - inputs.append(io.Combo.Input("model_c", options=folder_paths.get_filename_list(label))) + inputs.append(io.Combo.Input( + "model_c", + options=folder_paths.get_filename_list(label), + tooltip="Third equal-prior contributor. CWB derives its effective per-vector influence from consensus similarity.", + )) inputs.extend([ - io.Combo.Input("cwb_preset", options=CWB_PRESET_OPTIONS, default="baseline"), - io.Combo.Input("consensus_type", options=["mean", "median"], default="median"), - io.Combo.Input("alignment_method", options=["index", "similarity"], default="similarity"), - io.Float.Input("alignment_threshold", default=0.4, min=0.0, max=1.0, step=0.01), - io.Float.Input("similarity_threshold", default=0.0, min=-1.0, max=1.0, step=0.01), - io.Float.Input("power_alpha", default=2.0, min=0.0, max=10.0, step=0.1), - io.Float.Input("diversity_beta", default=0.0, min=0.0, max=10.0, step=0.1), - io.Boolean.Input("rescale_norm", default=False), - io.Float.Input("global_scale", default=1.0, min=0.0, max=10.0, step=0.01), - io.Boolean.Input("dynamic_similarity_contrast", default=False), - io.Boolean.Input("soft_comfort_bandpass", default=False), - io.Float.Input("position_weight", default=0.0, min=0.0, max=1.0, step=0.01), - io.Boolean.Input("preserve_common_prefix", default=False), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.String.Input("output_filename", default=default_filename), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), - io.Boolean.Input("glob_patterns", default=False), - io.Boolean.Input("lazy_load", default=True), - io.Boolean.Input("force_clear_cache", default=True), + io.Combo.Input( + "cwb_preset", + options=CWB_PRESET_OPTIONS, + default="baseline", + tooltip="Select custom to use every manual CWB control. Named presets set their listed controls; position/prefix controls, most alignment thresholds, and any explicit non-1.0 global scale remain active.", + ), + io.Combo.Input( + "consensus_type", + options=["mean", "median"], + default="median", + tooltip="Baseline vector used to score contributors. Median resists coordinate outliers; mean represents their arithmetic center. Manual value requires the custom preset.", + ), + io.Combo.Input( + "alignment_method", + options=["index", "similarity"], + default="similarity", + tooltip="Pair rows by original index or greedy cosine similarity. Row reordering applies only to embeddings and LoRA rank components; ordinary model tensors keep fixed coordinates.", + ), + io.Float.Input( + "alignment_threshold", + default=0.4, + min=0.0, + max=1.0, + step=0.01, + tooltip="Minimum original cosine score for a greedy row match. Applies only to similarity-aligned embeddings and LoRA rank components; unmatched rows do not contribute.", + ), + io.Float.Input( + "similarity_threshold", + default=0.0, + min=-1.0, + max=1.0, + step=0.01, + tooltip="Minimum cosine similarity to the mean/median consensus for a paired vector to receive weight. If every vector is rejected, CWB falls back to equal weights.", + ), + io.Float.Input( + "power_alpha", + default=2.0, + min=0.0, + max=10.0, + step=0.1, + tooltip="Exponent applied to accepted non-negative consensus similarities. Higher values concentrate weight on vectors closest to consensus. Manual value requires the custom preset.", + ), + io.Float.Input( + "diversity_beta", + default=0.0, + min=0.0, + max=10.0, + step=0.1, + tooltip="Bandpass exponent that suppresses near-consensus dominance to retain more variation. Zero disables diversity weighting. Manual value requires the custom preset.", + ), + io.Boolean.Input( + "rescale_norm", + default=False, + tooltip="Preserve vector energy after blending: keep the merged direction but set its L2 norm to the mean norm of participating vectors. Applied per row; for LoRA, separately per paired A row and B column.", + ), + io.Float.Input( + "global_scale", + default=1.0, + min=0.0, + max=10.0, + step=0.01, + tooltip="Multiply the final merged contribution. For LoRA it is applied once through B/up, not to both factors. Named presets remain neutral unless this is explicitly set away from 1.0.", + ), + io.Boolean.Input( + "dynamic_similarity_contrast", + default=False, + tooltip="Remap unequal consensus similarities into 0.7-1.0 before alpha/beta weighting to increase relative separation. It does not change row-alignment scores.", + ), + io.Boolean.Input( + "soft_comfort_bandpass", + default=False, + tooltip="When diversity_beta is positive, broaden its distance term from 1.001-similarity to 1.5-similarity. Has no effect when diversity_beta is zero.", + ), + io.Float.Input( + "position_weight", + default=0.0, + min=0.0, + max=1.0, + step=0.01, + tooltip="Blend positional proximity into greedy row selection: 0 uses cosine only; 1 uses position affinity only. Relevant only to similarity-aligned embeddings and LoRA rank components.", + ), + io.Boolean.Input( + "preserve_common_prefix", + default=False, + tooltip="Copy the longest numerically identical leading first-axis span from Model A before CWB. The current merger applies this to all tensors with at least two dimensions.", + ), + io.Combo.Input( + "mismatch_mode", + options=["skip", "zeros", "error"], + default="skip", + tooltip="For missing or incompatible anchored inputs: skip preserves the anchor, zeros inserts a zero contribution where possible, and error aborts. A lone secondary-only tensor is copied unchanged.", + ), + io.String.Input( + "output_filename", + default=default_filename, + tooltip="Filename without extension. The result is atomically written to this model category under ComfyUI's models directory.", + ), + io.Combo.Input( + "save_dtype", + options=["fp32", "fp16", "bf16"], + tooltip="Requested dtype for generated floating tensors. Participating FP32 inputs keep a result FP32 unless Override Dtype is enabled.", + ), + io.Combo.Input( + "process_device", + options=["cuda", "cpu"], + tooltip="Device used for per-layer FP32 CWB arithmetic. A CUDA out-of-memory error retries only the affected layer on CPU.", + ), + io.String.Input( + "exclude_patterns", + default="", + multiline=True, + tooltip="One pattern per line. Matching layers are preserved from the anchor instead of merged. Uses regex unless Glob Patterns is enabled.", + ), + io.String.Input( + "discard_patterns", + default="", + multiline=True, + tooltip="One pattern per line. Matching tensors or logical LoRA groups are omitted from the output. Uses regex unless Glob Patterns is enabled.", + ), + io.Boolean.Input( + "glob_patterns", + default=False, + tooltip="Interpret exclude and discard entries as shell-style glob patterns instead of regular expressions.", + ), + io.Boolean.Input( + "lazy_load", + default=True, + tooltip="Use UEL low-memory loading so tensors are read and released per work unit instead of retaining the complete inputs in RAM.", + ), + io.Boolean.Input( + "force_clear_cache", + default=True, + tooltip="Run Python garbage collection and clear the CUDA allocator cache before each layer. Reduces retained memory but can substantially slow merging.", + ), io.Boolean.Input( "override_dtype", default=False, @@ -1384,6 +1512,7 @@ class _CWBMergerNode(io.ComfyNode): node_id=cls.NODE_ID, display_name=cls.DISPLAY_NAME, category="ModelUtils/Merging", + description="Merge two or three safetensors files with Consensus-Weighted Blending. Use each input tooltip or Documentation Only for exact control scope.", inputs=inputs, outputs=[ io.String.Output(display_name="output_filename"), diff --git a/nodes/dora_extract_wd.py b/nodes/dora_extract_wd.py index 636da34..b3039b2 100644 --- a/nodes/dora_extract_wd.py +++ b/nodes/dora_extract_wd.py @@ -752,10 +752,10 @@ def _get_common_inputs(): tooltip="Clamp outlier singular values"), io.Float.Input("min_diff", default=0.0, min=0.0, max=1.0, step=0.001, tooltip="Skip layers with max difference below this"), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.String.Input("output_filename", default="extracted_lora"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible model tensors by skipping them, substituting zeros where supported, or aborting."), + io.String.Input("output_filename", default="extracted_lora", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save the extracted DoRA tensors."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for extraction arithmetic; CUDA out-of-memory processing falls back per affected layer where supported."), io.String.Input("skip_patterns", default="", multiline=True, tooltip="Patterns for layers to skip (regex or glob depending on glob_skip_patterns)"), io.Boolean.Input("glob_skip_patterns", default=False, diff --git a/nodes/dora_learned_wd.py b/nodes/dora_learned_wd.py index be38f07..39981c2 100644 --- a/nodes/dora_learned_wd.py +++ b/nodes/dora_learned_wd.py @@ -632,10 +632,10 @@ class DoRALearnedExtractRatio(io.ComfyNode): is_experimental=True, inputs=[ *_get_model_inputs(), - io.Float.Input("linear_ratio", default=2.0, min=1.0, max=100.0, step=0.1), - io.Float.Input("conv_ratio", default=2.0, min=1.0, max=100.0, step=0.1), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Ratio threshold for linear layers; higher values retain more singular values."), + io.Float.Input("conv_ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Ratio threshold for convolution layers; higher values retain more singular values."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_learned_inputs(), *_get_common_inputs(), ], @@ -675,10 +675,10 @@ class DoRALearnedExtractQuantile(io.ComfyNode): is_experimental=True, inputs=[ *_get_model_inputs(), - io.Float.Input("linear_quantile", default=0.9, min=0.0, max=1.0, step=0.01), - io.Float.Input("conv_quantile", default=0.9, min=0.0, max=1.0, step=0.01), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_quantile", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target cumulative singular-value fraction for linear layers."), + io.Float.Input("conv_quantile", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target cumulative singular-value fraction for convolution layers."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_learned_inputs(), *_get_common_inputs(), ], @@ -718,9 +718,9 @@ class DoRALearnedExtractKnee(io.ComfyNode): is_experimental=True, inputs=[ *_get_model_inputs(), - io.Combo.Input("knee_method", options=["sv_knee", "sv_cumulative_knee"], default="sv_knee"), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Combo.Input("knee_method", options=["sv_knee", "sv_cumulative_knee"], default="sv_knee", tooltip="Detect the knee from raw singular values or their cumulative distribution."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_learned_inputs(), *_get_common_inputs(), ], @@ -760,10 +760,10 @@ class DoRALearnedExtractFrobenius(io.ComfyNode): is_experimental=True, inputs=[ *_get_model_inputs(), - io.Float.Input("linear_target", default=0.9, min=0.0, max=1.0, step=0.01), - io.Float.Input("conv_target", default=0.9, min=0.0, max=1.0, step=0.01), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_target", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target Frobenius-norm fraction retained for linear layers."), + io.Float.Input("conv_target", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target Frobenius-norm fraction retained for convolution layers."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_learned_inputs(), *_get_common_inputs(), ], diff --git a/nodes/downloader_nodes.py b/nodes/downloader_nodes.py index f6f15ef..e74dab7 100644 --- a/nodes/downloader_nodes.py +++ b/nodes/downloader_nodes.py @@ -89,7 +89,7 @@ class CheckpointInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("checkpoints"), default="/")) + inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("checkpoints"), default="/", tooltip="Checkpoint subdirectory to scan; Recursive also includes its descendants.")) return io.Schema( node_id=cls.__name__, display_name="Checkpoint Downloader", @@ -126,7 +126,7 @@ class DiffusionModelInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("diffusion_models"), default="/")) + inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("diffusion_models"), default="/", tooltip="Diffusion-model subdirectory to scan; Recursive also includes its descendants.")) return io.Schema( node_id=cls.__name__, display_name="Diffusion Model Downloader", @@ -163,7 +163,7 @@ class LoRAInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("loras"), default="/")) + inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("loras"), default="/", tooltip="LoRA subdirectory to scan; Recursive also includes its descendants.")) return io.Schema( node_id=cls.__name__, display_name="LoRA Downloader", @@ -200,7 +200,7 @@ class EmbeddingInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("embeddings"), default="/")) + inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("embeddings"), default="/", tooltip="Embedding subdirectory to scan; Recursive also includes its descendants.")) return io.Schema( node_id=cls.__name__, display_name="Embedding Downloader", @@ -237,7 +237,7 @@ class VAEInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("vae"), default="/")) + inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("vae"), default="/", tooltip="VAE subdirectory to scan; Recursive also includes its descendants.")) return io.Schema( node_id=cls.__name__, display_name="VAE Downloader", @@ -274,7 +274,7 @@ class ControlNetInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("controlnet"), default="/")) + inputs.insert(0, io.Combo.Input("subdirectory", options=get_subdirectories("controlnet"), default="/", tooltip="ControlNet subdirectory to scan; Recursive also includes its descendants.")) return io.Schema( node_id=cls.__name__, display_name="ControlNet Downloader", @@ -311,7 +311,7 @@ class ManualPathInfoMetaDownloader(BaseInfoMetaDownloaderNode): @classmethod def define_schema(cls): inputs = list(cls.get_common_inputs().values()) - inputs.append(io.String.Input("scan_dir", default="")) + inputs.append(io.String.Input("scan_dir", default="", tooltip="Absolute directory to scan for supported model files.")) return io.Schema( node_id=cls.__name__, display_name="Manual Path Downloader", diff --git a/nodes/lora_extract_svd.py b/nodes/lora_extract_svd.py index 6d13446..594af99 100644 --- a/nodes/lora_extract_svd.py +++ b/nodes/lora_extract_svd.py @@ -755,11 +755,11 @@ def _get_common_inputs(): tooltip="Clamp outlier singular values"), io.Float.Input("min_diff", default=0.0, min=0.0, max=1.0, step=0.001, tooltip="Skip layers with max difference below this"), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.String.Input("output_filename", default="extracted_lora"), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible model tensors by skipping them, substituting zeros where supported, or aborting."), + io.String.Input("output_filename", default="extracted_lora", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Output dtype; layers stored as FP32 in either source remain FP32"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for extraction arithmetic; CUDA out-of-memory processing falls back per affected layer where supported."), io.String.Input("skip_patterns", default="", multiline=True, tooltip="Patterns for layers to skip (regex or glob depending on glob_skip_patterns)"), io.Boolean.Input("glob_skip_patterns", default=False, diff --git a/nodes/lora_rename.py b/nodes/lora_rename.py index 00eca48..5535142 100644 --- a/nodes/lora_rename.py +++ b/nodes/lora_rename.py @@ -104,8 +104,9 @@ class AnimaLoraRename(io.ComfyNode): io.Combo.Input( "lora_name", options=folder_paths.get_filename_list("loras"), + tooltip="Diffusers-format Anima LoRA whose tensor keys will be renamed.", ), - io.String.Input("output_filename", default="anima_lora_non_diffusers"), + io.String.Input("output_filename", default="anima_lora_non_diffusers", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), ], outputs=[ io.String.Output(display_name="output_path"), diff --git a/nodes/lora_resize.py b/nodes/lora_resize.py index d99a64f..d39d5be 100644 --- a/nodes/lora_resize.py +++ b/nodes/lora_resize.py @@ -920,9 +920,9 @@ class LoRAResizeFixed(io.ComfyNode): tooltip="LoRA to resize"), io.Int.Input("new_rank", default=64, min=1, max=3072, tooltip="Target rank"), - io.String.Input("output_filename", default="resized_lora"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.String.Input("output_filename", default="resized_lora", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save resized LoRA tensors."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for per-layer resize arithmetic; CUDA out-of-memory retries the affected layer on CPU."), io.Boolean.Input("force_clear_cache", default=False, tooltip="Clear CUDA cache after each layer (slower but saves VRAM)"), ], outputs=[io.String.Output(display_name="output_path")], @@ -958,9 +958,9 @@ class LoRAResizeRatio(io.ComfyNode): tooltip="Maximum allowed rank"), io.Float.Input("ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Keep SVs where S[i] > S[0]/ratio"), - io.String.Input("output_filename", default="resized_lora_ratio"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.String.Input("output_filename", default="resized_lora_ratio", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save resized LoRA tensors."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for per-layer resize arithmetic; CUDA out-of-memory retries the affected layer on CPU."), io.Boolean.Input("force_clear_cache", default=False, tooltip="Clear CUDA cache after each layer (slower but saves VRAM)"), ], outputs=[io.String.Output(display_name="output_path")], @@ -998,9 +998,9 @@ class LoRAResizeFrobenius(io.ComfyNode): tooltip="Minimum retained rank when layer dimensions permit"), io.Float.Input("target", default=0.9, min=0.1, max=1.0, step=0.01, tooltip="Target Frobenius norm retention (0.9 = 90%)"), - io.String.Input("output_filename", default="resized_lora_fro"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.String.Input("output_filename", default="resized_lora_fro", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save resized LoRA tensors."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for per-layer resize arithmetic; CUDA out-of-memory retries the affected layer on CPU."), io.Boolean.Input("force_clear_cache", default=False, tooltip="Clear CUDA cache after each layer (slower but saves VRAM)"), ], outputs=[io.String.Output(display_name="output_path")], @@ -1037,9 +1037,9 @@ class LoRAResizeCumulative(io.ComfyNode): tooltip="Maximum allowed rank"), io.Float.Input("target", default=0.9, min=0.1, max=1.0, step=0.01, tooltip="Target cumulative SV retention (0.9 = 90%)"), - io.String.Input("output_filename", default="resized_lora_cumulative"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.String.Input("output_filename", default="resized_lora_cumulative", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save resized LoRA tensors."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for per-layer resize arithmetic; CUDA out-of-memory retries the affected layer on CPU."), io.Boolean.Input("force_clear_cache", default=False, tooltip="Clear CUDA cache after each layer (slower but saves VRAM)"), ], outputs=[io.String.Output(display_name="output_path")], @@ -1842,9 +1842,9 @@ class LoRAMergeToModel(io.ComfyNode): # Settings io.String.Input("skip_patterns", default="", multiline=True, tooltip="Regex patterns for layers to skip"), - io.String.Input("output_filename", default="merged_model"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.String.Input("output_filename", default="merged_model", tooltip="Output filename without extension, written under ComfyUI's diffusion-model directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save the model after applying the LoRA."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for per-layer LoRA application; CUDA out-of-memory retries the affected layer on CPU."), io.Boolean.Input("lazy_load", default=True, tooltip="Low memory mode: load tensors from disk on demand"), io.Boolean.Input("force_clear_cache", default=False, tooltip="Clear CUDA cache after each layer (slower but saves VRAM)"), io.Boolean.Input("include_1d_diffs", default=False, diff --git a/nodes/merger.py b/nodes/merger.py index 22d11e8..eb946a2 100644 --- a/nodes/merger.py +++ b/nodes/merger.py @@ -357,24 +357,24 @@ class CheckpointTwoMerger(io.ComfyNode): display_name="Merge Checkpoints (2 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("checkpoints")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("checkpoints")), - io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_2_checkpoint"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("checkpoints"), tooltip="Primary checkpoint; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("checkpoints"), tooltip="Second checkpoint contributing to the selected calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES], tooltip="Two-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_2_checkpoint", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -423,24 +423,24 @@ class ModelTwoMerger(io.ComfyNode): display_name="Merge Models (2 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("diffusion_models")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("diffusion_models")), - io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_2_model"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("diffusion_models"), tooltip="Primary diffusion model; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("diffusion_models"), tooltip="Second diffusion model contributing to the selected calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES], tooltip="Two-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_2_model", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -489,24 +489,24 @@ class TextEncoderTwoMerger(io.ComfyNode): display_name="Merge Text Encoders (2 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("text_encoders")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("text_encoders")), - io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_2_textencoder"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("text_encoders"), tooltip="Primary text encoder; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("text_encoders"), tooltip="Second text encoder contributing to the selected calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES], tooltip="Two-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_2_textencoder", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -555,24 +555,24 @@ class LoRATwoMerger(io.ComfyNode): display_name="Merge LoRAs (2 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("loras")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("loras")), - io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_2_lora"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("loras"), tooltip="Primary LoRA; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("loras"), tooltip="Second LoRA contributing to the selected calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES], tooltip="Two-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_2_lora", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -625,24 +625,24 @@ class EmbeddingTwoMerger(io.ComfyNode): display_name="Merge Embeddings (2 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("embeddings")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("embeddings")), - io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_2_embedding"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("embeddings"), tooltip="Primary embedding; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("embeddings"), tooltip="Second embedding contributing to the selected calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in TWO_MODEL_MODES], tooltip="Two-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_2_embedding", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -693,25 +693,25 @@ class CheckpointThreeMerger(io.ComfyNode): display_name="Merge Checkpoints (3 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("checkpoints")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("checkpoints")), - io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("checkpoints")), - io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_3_checkpoint"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("checkpoints"), tooltip="Primary checkpoint; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("checkpoints"), tooltip="Second checkpoint contributing to the selected calculation mode."), + io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("checkpoints"), tooltip="Third checkpoint contributing to the selected three-model calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES], tooltip="Three-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_3_checkpoint", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -760,25 +760,25 @@ class ModelThreeMerger(io.ComfyNode): display_name="Merge Models (3 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("diffusion_models")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("diffusion_models")), - io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("diffusion_models")), - io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_3_model"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("diffusion_models"), tooltip="Primary diffusion model; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("diffusion_models"), tooltip="Second diffusion model contributing to the selected calculation mode."), + io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("diffusion_models"), tooltip="Third diffusion model contributing to the selected three-model calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES], tooltip="Three-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_3_model", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -827,25 +827,25 @@ class TextEncoderThreeMerger(io.ComfyNode): display_name="Merge Text Encoders (3 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("text_encoders")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("text_encoders")), - io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("text_encoders")), - io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_3_textencoder"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("text_encoders"), tooltip="Primary text encoder; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("text_encoders"), tooltip="Second text encoder contributing to the selected calculation mode."), + io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("text_encoders"), tooltip="Third text encoder contributing to the selected three-model calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES], tooltip="Three-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.99, min=-10.0, max=10.0, step=0.001, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_3_textencoder", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -894,25 +894,25 @@ class LoRAThreeMerger(io.ComfyNode): display_name="Merge LoRAs (3 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("loras")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("loras")), - io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("loras")), - io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_3_lora"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("loras"), tooltip="Primary LoRA; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("loras"), tooltip="Second LoRA contributing to the selected calculation mode."), + io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("loras"), tooltip="Third LoRA contributing to the selected three-model calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES], tooltip="Three-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_3_lora", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), @@ -965,25 +965,25 @@ class EmbeddingThreeMerger(io.ComfyNode): display_name="Merge Embeddings (3 Models)", category="ModelUtils/Merging", inputs=[ - io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("embeddings")), - io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("embeddings")), - io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("embeddings")), - io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES]), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop"), - io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01), - io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01), - io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01), - io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01), - io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), - io.String.Input("output_filename", default="merged_3_embedding"), - io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"]), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.String.Input("exclude_patterns", default="", multiline=True), - io.String.Input("discard_patterns", default="", multiline=True), + io.Combo.Input("execution_mode", options=["MERGE", "DOCUMENTATION ONLY"], tooltip="MERGE writes the selected result; DOCUMENTATION ONLY returns the operation reference without loading model files."), + io.Combo.Input("model_a", options=["None"] + folder_paths.get_filename_list("embeddings"), tooltip="Primary embedding; anchors metadata, tensor shapes, and values preserved by exclusions or skip handling."), + io.Combo.Input("model_b", options=["None"] + folder_paths.get_filename_list("embeddings"), tooltip="Second embedding contributing to the selected calculation mode."), + io.Combo.Input("model_c", options=["None"] + folder_paths.get_filename_list("embeddings"), tooltip="Third embedding contributing to the selected three-model calculation mode."), + io.Combo.Input("calc_mode", options=[m.name for m in THREE_MODEL_MODES], tooltip="Three-model operation to apply per comparable tensor; DOCUMENTATION ONLY shows its formula and coefficient meanings."), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible tensors: preserve Model A, substitute zeros where supported, or abort with an error."), + io.Combo.Input("alignment_mode", options=["pad/crop", "interpolate"], default="pad/crop", tooltip="Resolve compatible shape differences by zero-padding/cropping or by interpolating Model B and Model C to Model A shape."), + io.Float.Input("alpha", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; use DOCUMENTATION ONLY for its exact role in the selected calculation mode."), + io.Float.Input("beta", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("gamma", default=0.5, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("delta", default=2.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("epsilon", default=0.01, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Float.Input("zeta", default=0.0, min=-10.0, max=10.0, step=0.01, tooltip="Mode-specific coefficient; some calculation modes ignore it. See DOCUMENTATION ONLY for the selected formula."), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff, tooltip="Random seed used only by calculation modes with stochastic behavior."), + io.String.Input("output_filename", default="merged_3_embedding", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), + io.Combo.Input("save_dtype", options=["fp32", "fp16", "bf16"], tooltip="Output tensor dtype; when Override Dtype is disabled, source tensors with higher precision remain at that precision."), + io.Combo.Input("process_device", options=["cuda", "cpu"], tooltip="Device used for per-tensor merge arithmetic; CUDA out-of-memory retries the affected tensor on CPU."), + io.String.Input("exclude_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors excluded from merging and preserved from Model A."), + io.String.Input("discard_patterns", default="", multiline=True, tooltip="Newline-separated regex or glob patterns for tensors omitted entirely from the output."), io.Boolean.Input("glob_patterns", default=False, tooltip="When True, exclude/discard patterns use glob syntax (* = any sequence, dots are literal). " "When False (default), patterns are Python regex matched as substrings."), diff --git a/nodes/metakeys.py b/nodes/metakeys.py index 4a869f2..a02c3eb 100644 --- a/nodes/metakeys.py +++ b/nodes/metakeys.py @@ -56,6 +56,7 @@ class ModelMetaKeys(io.ComfyNode): io.Combo.Input( "diffusionmodel_name", options=folder_paths.get_filename_list("diffusion_models"), + tooltip="Diffusion model whose safetensors metadata and tensor keys will be listed.", ), ], outputs=[ @@ -82,6 +83,7 @@ class TextEncoderMetaKeys(io.ComfyNode): io.Combo.Input( "textencoder_name", options=folder_paths.get_filename_list("text_encoders"), + tooltip="Text encoder whose safetensors metadata and tensor keys will be listed.", ), ], outputs=[ @@ -108,6 +110,7 @@ class LoRAMetaKeys(io.ComfyNode): io.Combo.Input( "lora_name", options=folder_paths.get_filename_list("loras"), + tooltip="LoRA whose safetensors metadata and tensor keys will be listed.", ), ], outputs=[ @@ -134,6 +137,7 @@ class CheckpointMetaKeys(io.ComfyNode): io.Combo.Input( "ckpt_name", options=folder_paths.get_filename_list("checkpoints"), + tooltip="Checkpoint whose safetensors metadata and tensor keys will be listed.", ), ], outputs=[ @@ -160,6 +164,7 @@ class EmbeddingMetaKeys(io.ComfyNode): io.Combo.Input( "embedding", options=folder_paths.get_filename_list("embeddings"), + tooltip="Embedding whose safetensors metadata and tensor keys will be listed.", ), ], outputs=[ diff --git a/nodes/model_analysis.py b/nodes/model_analysis.py index a6c6b5e..e6d409b 100644 --- a/nodes/model_analysis.py +++ b/nodes/model_analysis.py @@ -1025,9 +1025,21 @@ class ModelAnalysisLogic: def _common_inputs(model_type: str, *, alignment_control: bool): inputs = [ - io.Combo.Input("execution_mode", options=["ANALYZE", "DOCUMENTATION ONLY"]), - io.Combo.Input("model_a", options=folder_paths.get_filename_list(model_type)), - io.Combo.Input("model_b", options=folder_paths.get_filename_list(model_type)), + io.Combo.Input( + "execution_mode", + options=["ANALYZE", "DOCUMENTATION ONLY"], + tooltip="ANALYZE streams both files and produces standard and CWB diagnostic reports. DOCUMENTATION ONLY performs no model loading.", + ), + io.Combo.Input( + "model_a", + options=folder_paths.get_filename_list(model_type), + tooltip="First file in the comparison. Reports identify values and keys unique to this input separately from Model B.", + ), + io.Combo.Input( + "model_b", + options=folder_paths.get_filename_list(model_type), + tooltip="Second file in the comparison. Inputs are analyzed only; neither file is modified or merged.", + ), ] if alignment_control: inputs.append(io.Boolean.Input( @@ -1039,9 +1051,23 @@ def _common_inputs(model_type: str, *, alignment_control: bool): ), )) inputs.extend([ - io.Int.Input("top_weight_differences", default=20, min=0, max=1000), - io.Combo.Input("process_device", options=["cuda", "cpu"]), - io.Boolean.Input("force_clear_cache", default=True), + io.Int.Input( + "top_weight_differences", + default=20, + min=0, + max=1000, + tooltip="Number of largest individual absolute parameter differences retained for the detailed report. Zero disables this list.", + ), + io.Combo.Input( + "process_device", + options=["cuda", "cpu"], + tooltip="Device for per-work-unit floating-point analysis. A CUDA OOM retries only the affected unit on CPU.", + ), + io.Boolean.Input( + "force_clear_cache", + default=True, + tooltip="Run garbage collection and clear the CUDA allocator cache after every analyzed work unit. Saves retained memory but slows analysis.", + ), ]) return inputs @@ -1059,6 +1085,7 @@ class _ModelAnalysisNode(io.ComfyNode): node_id=cls.NODE_ID, display_name=cls.DISPLAY_NAME, category="ModelUtils/Analysis", + description="Compare two model files without merging them and return separate standard-metric and CWB-diagnostic reports.", inputs=_common_inputs( cls.MODEL_TYPE, alignment_control=cls.EMBEDDING_ALIGNMENT or cls.LORA_MODE, diff --git a/nodes/model_info_nodes.py b/nodes/model_info_nodes.py index 8ade4e1..a22b107 100644 --- a/nodes/model_info_nodes.py +++ b/nodes/model_info_nodes.py @@ -53,8 +53,8 @@ class CheckpointInfoLoader(BaseModelInfoLoader): display_name="Checkpoint Info Loader", category=cls.CATEGORY, inputs=[ - io.Combo.Input("checkpoint", options=folder_paths.get_filename_list("checkpoints")), - io.Int.Input("workflow_index", default=0, min=0, max=100) + io.Combo.Input("checkpoint", options=folder_paths.get_filename_list("checkpoints"), tooltip="Checkpoint whose downloaded information and example workflow will be returned."), + io.Int.Input("workflow_index", default=0, min=0, max=100, tooltip="Zero-based index of the saved example workflow to return when multiple workflows are available.") ], outputs=[ io.Image.Output(display_name="preview"), @@ -76,8 +76,8 @@ class LoRAInfoLoader(BaseModelInfoLoader): display_name="LoRA Info Loader", category=cls.CATEGORY, inputs=[ - io.Combo.Input("lora", options=folder_paths.get_filename_list("loras")), - io.Int.Input("workflow_index", default=0, min=0, max=100) + io.Combo.Input("lora", options=folder_paths.get_filename_list("loras"), tooltip="LoRA whose downloaded information and example workflow will be returned."), + io.Int.Input("workflow_index", default=0, min=0, max=100, tooltip="Zero-based index of the saved example workflow to return when multiple workflows are available.") ], outputs=[ io.Image.Output(display_name="preview"), @@ -99,8 +99,8 @@ class EmbeddingInfoLoader(BaseModelInfoLoader): display_name="Embedding Info Loader", category=cls.CATEGORY, inputs=[ - io.Combo.Input("embedding", options=folder_paths.get_filename_list("embeddings")), - io.Int.Input("workflow_index", default=0, min=0, max=100) + io.Combo.Input("embedding", options=folder_paths.get_filename_list("embeddings"), tooltip="Embedding whose downloaded information and example workflow will be returned."), + io.Int.Input("workflow_index", default=0, min=0, max=100, tooltip="Zero-based index of the saved example workflow to return when multiple workflows are available.") ], outputs=[ io.Image.Output(display_name="preview"), @@ -122,8 +122,8 @@ class VAEInfoLoader(BaseModelInfoLoader): display_name="VAE Info Loader", category=cls.CATEGORY, inputs=[ - io.Combo.Input("vae", options=folder_paths.get_filename_list("vae")), - io.Int.Input("workflow_index", default=0, min=0, max=100) + io.Combo.Input("vae", options=folder_paths.get_filename_list("vae"), tooltip="VAE whose downloaded information and example workflow will be returned."), + io.Int.Input("workflow_index", default=0, min=0, max=100, tooltip="Zero-based index of the saved example workflow to return when multiple workflows are available.") ], outputs=[ io.Image.Output(display_name="preview"), @@ -145,8 +145,8 @@ class ControlNetInfoLoader(BaseModelInfoLoader): display_name="ControlNet Info Loader", category=cls.CATEGORY, inputs=[ - io.Combo.Input("controlnet", options=folder_paths.get_filename_list("controlnet")), - io.Int.Input("workflow_index", default=0, min=0, max=100) + io.Combo.Input("controlnet", options=folder_paths.get_filename_list("controlnet"), tooltip="ControlNet whose downloaded information and example workflow will be returned."), + io.Int.Input("workflow_index", default=0, min=0, max=100, tooltip="Zero-based index of the saved example workflow to return when multiple workflows are available.") ], outputs=[ io.Image.Output(display_name="preview"), @@ -168,8 +168,8 @@ class DiffusionModelInfoLoader(BaseModelInfoLoader): display_name="Diffusion Model Info Loader", category=cls.CATEGORY, inputs=[ - io.Combo.Input("diffusion_model", options=folder_paths.get_filename_list("diffusion_models")), - io.Int.Input("workflow_index", default=0, min=0, max=100) + io.Combo.Input("diffusion_model", options=folder_paths.get_filename_list("diffusion_models"), tooltip="Diffusion model whose downloaded information and example workflow will be returned."), + io.Int.Input("workflow_index", default=0, min=0, max=100, tooltip="Zero-based index of the saved example workflow to return when multiple workflows are available.") ], outputs=[ io.Image.Output(display_name="preview"), diff --git a/nodes/prunekeys.py b/nodes/prunekeys.py index 2201357..8630cb6 100644 --- a/nodes/prunekeys.py +++ b/nodes/prunekeys.py @@ -83,10 +83,11 @@ class ModelPruneKeys(io.ComfyNode): io.Combo.Input( "diffusionmodel_name", options=folder_paths.get_filename_list("diffusion_models"), + tooltip="Diffusion model from which matching tensor keys will be removed.", ), - io.String.Input("keys_to_prune", multiline=True, default=""), - io.Boolean.Input("use_regex", default=False), - io.String.Input("output_filename", default="pruned_model"), + io.String.Input("keys_to_prune", multiline=True, default="", tooltip="One tensor-key pattern per line. Blank lines are ignored."), + io.Boolean.Input("use_regex", default=False, tooltip="Interpret each line as a regular expression; otherwise match literal key text."), + io.String.Input("output_filename", default="pruned_model", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -113,10 +114,11 @@ class TextEncoderPruneKeys(io.ComfyNode): io.Combo.Input( "textencoder_name", options=folder_paths.get_filename_list("text_encoders"), + tooltip="Text encoder from which matching tensor keys will be removed.", ), - io.String.Input("keys_to_prune", multiline=True, default=""), - io.Boolean.Input("use_regex", default=False), - io.String.Input("output_filename", default="pruned_textencoder"), + io.String.Input("keys_to_prune", multiline=True, default="", tooltip="One tensor-key pattern per line. Blank lines are ignored."), + io.Boolean.Input("use_regex", default=False, tooltip="Interpret each line as a regular expression; otherwise match literal key text."), + io.String.Input("output_filename", default="pruned_textencoder", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -143,10 +145,11 @@ class LoRAPruneKeys(io.ComfyNode): io.Combo.Input( "lora_name", options=folder_paths.get_filename_list("loras"), + tooltip="LoRA from which matching tensor keys will be removed.", ), - io.String.Input("keys_to_prune", multiline=True, default=""), - io.Boolean.Input("use_regex", default=False), - io.String.Input("output_filename", default="pruned_lora"), + io.String.Input("keys_to_prune", multiline=True, default="", tooltip="One tensor-key pattern per line. Blank lines are ignored."), + io.Boolean.Input("use_regex", default=False, tooltip="Interpret each line as a regular expression; otherwise match literal key text."), + io.String.Input("output_filename", default="pruned_lora", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -173,10 +176,11 @@ class CheckpointPruneKeys(io.ComfyNode): io.Combo.Input( "ckpt_name", options=folder_paths.get_filename_list("checkpoints"), + tooltip="Checkpoint from which matching tensor keys will be removed.", ), - io.String.Input("keys_to_prune", multiline=True, default=""), - io.Boolean.Input("use_regex", default=False), - io.String.Input("output_filename", default="pruned_checkpoint"), + io.String.Input("keys_to_prune", multiline=True, default="", tooltip="One tensor-key pattern per line. Blank lines are ignored."), + io.Boolean.Input("use_regex", default=False, tooltip="Interpret each line as a regular expression; otherwise match literal key text."), + io.String.Input("output_filename", default="pruned_checkpoint", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -203,10 +207,11 @@ class EmbeddingPruneKeys(io.ComfyNode): io.Combo.Input( "embedding", options=folder_paths.get_filename_list("embeddings"), + tooltip="Embedding from which matching tensor keys will be removed.", ), - io.String.Input("keys_to_prune", multiline=True, default=""), - io.Boolean.Input("use_regex", default=False), - io.String.Input("output_filename", default="pruned_embedding"), + io.String.Input("keys_to_prune", multiline=True, default="", tooltip="One tensor-key pattern per line. Blank lines are ignored."), + io.Boolean.Input("use_regex", default=False, tooltip="Interpret each line as a regular expression; otherwise match literal key text."), + io.String.Input("output_filename", default="pruned_embedding", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -218,4 +223,4 @@ class EmbeddingPruneKeys(io.ComfyNode): use_regex: bool, output_filename: str) -> io.NodeOutput: path = _prune_keys(embedding, "embeddings", keys_to_prune, use_regex, output_filename) - return io.NodeOutput(path) \ No newline at end of file + return io.NodeOutput(path) diff --git a/nodes/renamekeys.py b/nodes/renamekeys.py index 1bb1612..ece6323 100644 --- a/nodes/renamekeys.py +++ b/nodes/renamekeys.py @@ -78,10 +78,11 @@ class ModelRenameKeys(io.ComfyNode): io.Combo.Input( "diffusionmodel_name", options=folder_paths.get_filename_list("diffusion_models"), + tooltip="Diffusion model whose tensor keys will be renamed.", ), - io.String.Input("old_keys", multiline=True, default=""), - io.String.Input("new_keys", multiline=True, default=""), - io.String.Input("output_filename", default="renamed_model"), + io.String.Input("old_keys", multiline=True, default="", tooltip="Existing key text or pattern, one entry per line, paired by line with New Keys."), + io.String.Input("new_keys", multiline=True, default="", tooltip="Replacement key text, one entry per line, paired by line with Old Keys."), + io.String.Input("output_filename", default="renamed_model", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -108,10 +109,11 @@ class TextEncoderRenameKeys(io.ComfyNode): io.Combo.Input( "textencoder_name", options=folder_paths.get_filename_list("text_encoders"), + tooltip="Text encoder whose tensor keys will be renamed.", ), - io.String.Input("old_keys", multiline=True, default=""), - io.String.Input("new_keys", multiline=True, default=""), - io.String.Input("output_filename", default="renamed_textencoder"), + io.String.Input("old_keys", multiline=True, default="", tooltip="Existing key text or pattern, one entry per line, paired by line with New Keys."), + io.String.Input("new_keys", multiline=True, default="", tooltip="Replacement key text, one entry per line, paired by line with Old Keys."), + io.String.Input("output_filename", default="renamed_textencoder", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -138,10 +140,11 @@ class LoRARenameKeys(io.ComfyNode): io.Combo.Input( "lora_name", options=folder_paths.get_filename_list("loras"), + tooltip="LoRA whose tensor keys will be renamed.", ), - io.String.Input("old_keys", multiline=True, default=""), - io.String.Input("new_keys", multiline=True, default=""), - io.String.Input("output_filename", default="renamed_lora"), + io.String.Input("old_keys", multiline=True, default="", tooltip="Existing key text or pattern, one entry per line, paired by line with New Keys."), + io.String.Input("new_keys", multiline=True, default="", tooltip="Replacement key text, one entry per line, paired by line with Old Keys."), + io.String.Input("output_filename", default="renamed_lora", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -168,10 +171,11 @@ class CheckpointRenameKeys(io.ComfyNode): io.Combo.Input( "ckpt_name", options=folder_paths.get_filename_list("checkpoints"), + tooltip="Checkpoint whose tensor keys will be renamed.", ), - io.String.Input("old_keys", multiline=True, default=""), - io.String.Input("new_keys", multiline=True, default=""), - io.String.Input("output_filename", default="renamed_checkpoint"), + io.String.Input("old_keys", multiline=True, default="", tooltip="Existing key text or pattern, one entry per line, paired by line with New Keys."), + io.String.Input("new_keys", multiline=True, default="", tooltip="Replacement key text, one entry per line, paired by line with Old Keys."), + io.String.Input("output_filename", default="renamed_checkpoint", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -198,10 +202,11 @@ class EmbeddingRenameKeys(io.ComfyNode): io.Combo.Input( "embedding", options=folder_paths.get_filename_list("embeddings"), + tooltip="Embedding whose tensor keys will be renamed.", ), - io.String.Input("old_keys", multiline=True, default=""), - io.String.Input("new_keys", multiline=True, default=""), - io.String.Input("output_filename", default="renamed_embedding"), + io.String.Input("old_keys", multiline=True, default="", tooltip="Existing key text or pattern, one entry per line, paired by line with New Keys."), + io.String.Input("new_keys", multiline=True, default="", tooltip="Replacement key text, one entry per line, paired by line with Old Keys."), + io.String.Input("output_filename", default="renamed_embedding", tooltip="Output filename without extension, written under the matching ComfyUI model directory."), ], outputs=[ io.String.Output(display_name="output_path"), @@ -213,4 +218,4 @@ class EmbeddingRenameKeys(io.ComfyNode): new_keys: str, output_filename: str) -> io.NodeOutput: path = _rename_keys(embedding, "embeddings", old_keys, new_keys, output_filename) - return io.NodeOutput(path) \ No newline at end of file + return io.NodeOutput(path) diff --git a/nodes/text_encoder_extract.py b/nodes/text_encoder_extract.py index 562cc35..020433b 100644 --- a/nodes/text_encoder_extract.py +++ b/nodes/text_encoder_extract.py @@ -305,10 +305,10 @@ def _get_common_inputs(): tooltip="Clamp outlier singular values"), io.Float.Input("min_diff", default=0.0, min=0.0, max=1.0, step=0.001, tooltip="Skip layers with max difference below this"), - io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip"), - io.String.Input("output_filename", default="extracted_te_lora"), - io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"), - io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"), + io.Combo.Input("mismatch_mode", options=["skip", "zeros", "error"], default="skip", tooltip="Handle missing or incompatible text-encoder tensors by skipping them, substituting zeros where supported, or aborting."), + io.String.Input("output_filename", default="extracted_te_lora", tooltip="Output filename without extension, written under the ComfyUI LoRA directory."), + io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Data type used to save extracted text-encoder LoRA factors."), + io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device used for per-layer extraction arithmetic; CUDA out-of-memory retries the affected layer on CPU where supported."), io.String.Input("skip_patterns", default="", multiline=True, tooltip="Patterns for layers to skip (regex or glob depending on glob_skip_patterns)"), io.Boolean.Input("glob_skip_patterns", default=False, @@ -370,10 +370,10 @@ class TextEncoderLoRAExtractRatio(io.ComfyNode): description="Keep singular values > max(S) / ratio.", inputs=[ *_get_te_model_inputs(), - io.Float.Input("linear_ratio", default=2.0, min=1.0, max=100.0, step=0.1), - io.Float.Input("conv_ratio", default=2.0, min=1.0, max=100.0, step=0.1), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Ratio threshold for linear layers; higher values retain more singular values."), + io.Float.Input("conv_ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Ratio threshold for convolution layers; higher values retain more singular values."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -410,10 +410,10 @@ class TextEncoderLoRAExtractQuantile(io.ComfyNode): description="Keep enough singular values to reach target percentage.", inputs=[ *_get_te_model_inputs(), - io.Float.Input("linear_quantile", default=0.9, min=0.0, max=1.0, step=0.01), - io.Float.Input("conv_quantile", default=0.9, min=0.0, max=1.0, step=0.01), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_quantile", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target cumulative singular-value fraction for linear layers."), + io.Float.Input("conv_quantile", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target cumulative singular-value fraction for convolution layers."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -450,9 +450,9 @@ class TextEncoderLoRAExtractKnee(io.ComfyNode): description="Automatically find optimal rank using knee detection on singular value curve.", inputs=[ *_get_te_model_inputs(), - io.Combo.Input("knee_method", options=["sv_knee", "sv_cumulative_knee"], default="sv_knee"), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Combo.Input("knee_method", options=["sv_knee", "sv_cumulative_knee"], default="sv_knee", tooltip="Detect the knee from raw singular values or their cumulative distribution."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -489,10 +489,10 @@ class TextEncoderLoRAExtractFrobenius(io.ComfyNode): description="Preserve target fraction of Frobenius norm.", inputs=[ *_get_te_model_inputs(), - io.Float.Input("linear_target", default=0.9, min=0.0, max=1.0, step=0.01), - io.Float.Input("conv_target", default=0.9, min=0.0, max=1.0, step=0.01), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_target", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target Frobenius-norm fraction retained for linear layers."), + io.Float.Input("conv_target", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target Frobenius-norm fraction retained for convolution layers."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -572,10 +572,10 @@ class TextEncoderDoRAExtractRatio(io.ComfyNode): description="Keep singular values > max(S) / ratio.", inputs=[ *_get_te_model_inputs(), - io.Float.Input("linear_ratio", default=2.0, min=1.0, max=100.0, step=0.1), - io.Float.Input("conv_ratio", default=2.0, min=1.0, max=100.0, step=0.1), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Ratio threshold for linear layers; higher values retain more singular values."), + io.Float.Input("conv_ratio", default=2.0, min=1.0, max=100.0, step=0.1, tooltip="Ratio threshold for convolution layers; higher values retain more singular values."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -612,10 +612,10 @@ class TextEncoderDoRAExtractQuantile(io.ComfyNode): description="Keep enough singular values to reach target percentage.", inputs=[ *_get_te_model_inputs(), - io.Float.Input("linear_quantile", default=0.9, min=0.0, max=1.0, step=0.01), - io.Float.Input("conv_quantile", default=0.9, min=0.0, max=1.0, step=0.01), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_quantile", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target cumulative singular-value fraction for linear layers."), + io.Float.Input("conv_quantile", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target cumulative singular-value fraction for convolution layers."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -652,9 +652,9 @@ class TextEncoderDoRAExtractKnee(io.ComfyNode): description="Automatically find optimal rank using knee detection on singular value curve.", inputs=[ *_get_te_model_inputs(), - io.Combo.Input("knee_method", options=["sv_knee", "sv_cumulative_knee"], default="sv_knee"), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Combo.Input("knee_method", options=["sv_knee", "sv_cumulative_knee"], default="sv_knee", tooltip="Detect the knee from raw singular values or their cumulative distribution."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], @@ -691,10 +691,10 @@ class TextEncoderDoRAExtractFrobenius(io.ComfyNode): description="Preserve target fraction of Frobenius norm.", inputs=[ *_get_te_model_inputs(), - io.Float.Input("linear_target", default=0.9, min=0.0, max=1.0, step=0.01), - io.Float.Input("conv_target", default=0.9, min=0.0, max=1.0, step=0.01), - io.Int.Input("linear_max_rank", default=128, min=1, max=16384), - io.Int.Input("conv_max_rank", default=128, min=1, max=16384), + io.Float.Input("linear_target", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target Frobenius-norm fraction retained for linear layers."), + io.Float.Input("conv_target", default=0.9, min=0.0, max=1.0, step=0.01, tooltip="Target Frobenius-norm fraction retained for convolution layers."), + io.Int.Input("linear_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for linear layers."), + io.Int.Input("conv_max_rank", default=128, min=1, max=16384, tooltip="Maximum extracted rank for convolution layers."), *_get_common_inputs(), ], outputs=[io.String.Output(display_name="output_path")], diff --git a/tests/test_consensus_merger.py b/tests/test_consensus_merger.py index a893c43..af50ccc 100644 --- a/tests/test_consensus_merger.py +++ b/tests/test_consensus_merger.py @@ -332,6 +332,8 @@ def test_all_ten_schemas_and_lora_switch_contract(cwb): for node in cwb.CWB_MERGER_NODES: schema = node.define_schema() ids = [value.id for value in schema.inputs] + assert schema.description + assert all(value.tooltip for value in schema.inputs) assert ids[:3] == ["execution_mode", "model_a", "model_b"] if node.INPUT_COUNT == 3: assert ids[3] == "model_c" diff --git a/tests/test_model_analysis.py b/tests/test_model_analysis.py index cf386b1..3facc98 100644 --- a/tests/test_model_analysis.py +++ b/tests/test_model_analysis.py @@ -43,6 +43,8 @@ def test_five_node_schema_contract(analysis): for node in analysis.MODEL_ANALYSIS_NODES: schema = node.define_schema() assert schema.category == "ModelUtils/Analysis" + assert schema.description + assert all(item.tooltip for item in schema.inputs) assert [item.id for item in schema.inputs[:3]] == [ "execution_mode", "model_a", "model_b", ]