diff --git a/node_mappings.py b/node_mappings.py index f748fa4..4ce98f1 100644 --- a/node_mappings.py +++ b/node_mappings.py @@ -178,6 +178,8 @@ NODE_CLASS_MAPPINGS = { "TT_SdxlModelsLoaderAdvancedNode": TT_SdxlModelsLoaderAdvancedNode, "TT_FluxModelsLoaderNode": TT_FluxModelsLoaderNode, "TT_FluxModelsLoaderAdvancedNode": TT_FluxModelsLoaderAdvancedNode, + "TT_GgufModelsLoaderNode": TT_GgufModelsLoaderNode, + "TT_GgufModelsLoaderAdvancedNode": TT_GgufModelsLoaderAdvancedNode, # Postproduction "TT_ApplyLut": TT_ApplyLut, "TT_AddFilmGrain": TT_AddFilmGrain, @@ -278,6 +280,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "TT_SdxlModelsLoaderAdvancedNode": "TT SDXL Models Loader (Advanced)", "TT_FluxModelsLoaderNode": "TT FLUX Models Loader", "TT_FluxModelsLoaderAdvancedNode": "TT FLUX Models Loader (Advanced)", + "TT_GgufModelsLoaderNode": "TT GGUF Models Loader", + "TT_GgufModelsLoaderAdvancedNode": "TT GGUF Models Loader (Advanced)", # Postproduction "TT_ApplyLut": "TT Apply LUT", "TT_AddFilmGrain": "TT Add Film Grain", diff --git a/nodes/loaders/gguf_models_loader.py b/nodes/loaders/gguf_models_loader.py index 0342032..8337e6b 100644 --- a/nodes/loaders/gguf_models_loader.py +++ b/nodes/loaders/gguf_models_loader.py @@ -28,7 +28,7 @@ class TT_Flux2GgufModelsLoader(): RETURN_TYPES = ("MODEL", "CLIP", "VAE",) RETURN_NAMES = ("MODEL", "CLIP", "VAE",) FUNCTION = "load_models" - CATEGORY = "TenserTensor/Loaders/FLUX2 GGUF" + CATEGORY = "TenserTensor/Deprecated/Loaders/FLUX2 GGUF" VAE_DEVICE = "cpu" VAE_DTYPE = "bfloat16" diff --git a/nodes/loaders/gguf_models_loader_advanced.py b/nodes/loaders/gguf_models_loader_advanced.py index 7ac47ab..eb32531 100644 --- a/nodes/loaders/gguf_models_loader_advanced.py +++ b/nodes/loaders/gguf_models_loader_advanced.py @@ -55,7 +55,7 @@ class TT_Flux2GgufModelsLoaderAdvanced(): RETURN_TYPES = ("MODEL", "CLIP", "VAE",) RETURN_NAMES = ("MODEL", "CLIP", "VAE",) FUNCTION = "load_models" - CATEGORY = "TenserTensor/Loaders/FLUX2 GGUF" + CATEGORY = "TenserTensor/Deprecated/Loaders/FLUX2 GGUF" def load_models(self, **kwargs): model = load_unet( diff --git a/nodes_loaders.py b/nodes_loaders.py index 6ccbae7..11bf6e5 100644 --- a/nodes_loaders.py +++ b/nodes_loaders.py @@ -1,13 +1,26 @@ # (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0) + +# Contains slightly modified code by City96 +# (c) City96 || Apache-2.0 (apache.org/licenses/LICENSE-2.0) +# Modified by TenserTensor +# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0) + +import collections +import inspect +import logging import os from typing import override +import torch from torch import bfloat16, float16, float32, float8_e4m3fn, float8_e5m2, device, tensor import folder_paths -from comfy import sd, model_management, utils, model_sampling +from comfy import sd, model_management, utils, model_sampling, model_patcher, lora, float from comfy_api.latest import io, ComfyExtension from nodes import VAELoader, MAX_RESOLUTION +from .gguf.dequant import is_quantized, is_torch_compatible +from .gguf.loader import gguf_sd_loader, gguf_clip_loader +from .gguf.ops import GGMLOps, move_patch_to_device CATEGORY = "TenserTensor/Loaders" TORCH_DEVICES = ["default", "cpu"] @@ -27,10 +40,22 @@ def get_diffusion_models_files(): return folder_paths.get_filename_list("diffusion_models") +def get_gguf_diffusion_models_files(): + update_folder_names_and_paths("unet_gguf", ["diffusion_models", "unet"]) + + return [f for f in folder_paths.get_filename_list("unet_gguf")] + + def get_text_encoder_files(): return folder_paths.get_filename_list("text_encoders") +def get_gguf_text_encoder_files(): + update_folder_names_and_paths("clip_gguf", ["text_encoders", "clip"]) + + return [x for x in folder_paths.get_filename_list("clip_gguf")] + + def get_vae_files(): return VAELoader.vae_list(VAELoader) @@ -380,6 +405,258 @@ class TT_FluxModelsLoaderAdvancedNode(io.ComfyNode): return io.NodeOutput(model, clip, vae) +def update_folder_names_and_paths(key, targets=[]): + base = folder_paths.folder_names_and_paths.get(key, ([], {})) + base = base[0] if isinstance(base[0], (list, set, tuple)) else [] + + target = next((x for x in targets if x in folder_paths.folder_names_and_paths), targets[0]) + orig, _ = folder_paths.folder_names_and_paths.get(target, ([], {})) + folder_paths.folder_names_and_paths[key] = (orig or base, {".gguf"}) + if base and base != orig: + logging.warning(f"Unknown file list already present on key {key}: {base}") + + +WeightBackup = collections.namedtuple('WeightBackup', ['weight', 'inplace_update']) + + +class GGUFModelPatcher(model_patcher.ModelPatcher): + patch_on_device = False + + def patch_weight_to_device(self, key, device_to=None, inplace_update=False): + if key not in self.patches: + return + + weight = utils.get_attr(self.model, key) + + patches = self.patches[key] + if is_quantized(weight): + out_weight = weight.to(device_to) + patches = move_patch_to_device(patches, self.load_device if self.patch_on_device else self.offload_device) + # TODO: do we ever have legitimate duplicate patches? (i.e. patch on top of patched weight) + out_weight.patches = [(patches, key)] + else: + inplace_update = self.weight_inplace_update or inplace_update + if key not in self.backup: + self.backup[key] = WeightBackup( + weight.to(device=self.offload_device, copy=inplace_update), inplace_update + ) + + if device_to is not None: + temp_weight = model_management.cast_to_device(weight, device_to, torch.float32, copy=True) + else: + temp_weight = weight.to(torch.float32, copy=True) + + out_weight = lora.calculate_weight(patches, temp_weight, key) + out_weight = float.stochastic_rounding(out_weight, weight.dtype) + + if inplace_update: + utils.copy_to_param(self.model, key, out_weight) + else: + utils.set_attr_param(self.model, key, out_weight) + + def unpatch_model(self, device_to=None, unpatch_weights=True): + if unpatch_weights: + for p in self.model.parameters(): + if is_torch_compatible(p): + continue + patches = getattr(p, "patches", []) + if len(patches) > 0: + p.patches = [] + # TODO: Find another way to not unload after patches + return super().unpatch_model(device_to=device_to, unpatch_weights=unpatch_weights) + + +def get_dtype(dtype): + match dtype: + case None | "default": + return None + case "target": + return dtype + case _: + return getattr(torch, dtype) + + +def load_clip_data(ckpt_paths): + clip_data = [] + for path in ckpt_paths: + if path.endswith(".gguf"): + state_dict = gguf_clip_loader(path) + else: + state_dict = utils.load_torch_file(path, safe_load=True) + # NOTE: Scaled FP8 would require different custom ops, but only one can be active + if "scaled_fp8" in state_dict: + raise NotImplementedError( + f"Mixing scaled FP8 with GGUF is not supported! Use regular CLIP loader or switch model(s)\n({path})") + + clip_data.append(state_dict) + + return clip_data + + +def load_clip_patcher(clip_name, type="flux2"): + clip_path = folder_paths.get_full_path("clip_gguf", clip_name) + clip_type = getattr(sd.CLIPType, type.upper(), sd.CLIPType.FLUX2) + clip_data = load_clip_data([clip_path]) + + clip = sd.load_text_encoder_state_dicts( + clip_type=clip_type, + state_dicts=clip_data, + model_options={ + "custom_operations": GGMLOps, + "initial_device": model_management.text_encoder_offload_device() + }, + embedding_directory=folder_paths.get_folder_paths("embeddings"), + ) + clip.patcher = GGUFModelPatcher.clone(clip.patcher) + + return clip + + +def apply_bypass_lora_for_models(loaded_lora, model, clip, lora_name, strength): + if loaded_lora == None: + lora_path = folder_paths.get_full_path_or_raise("loras", lora_name) + lora = utils.load_torch_file(lora_path, safe_load=True) + else: + lora = loaded_lora + + patched_model, patched_clip = sd.load_bypass_lora_for_models(model, clip, lora, strength, strength) + + return (patched_model, patched_clip, lora,) + + +def load_gguf_pipeline(cls, **kwargs): + ops = GGMLOps() + + ops.Linear.dequant_dtype = get_dtype(kwargs.get("dequant_dtype", None)) + ops.Linear.patch_dtype = get_dtype(kwargs.get("patch_dtype", None)) + + gguf_full_path = folder_paths.get_full_path("unet_gguf", kwargs.get("diffusion_model")) + state_dict, extra = gguf_sd_loader(gguf_full_path) + + args = {} + valid_params = inspect.signature(sd.load_diffusion_model_state_dict).parameters + + if "metadata" in valid_params: + args["metadata"] = extra.get("metadata", {}) + + model = sd.load_diffusion_model_state_dict(state_dict, model_options={"custom_operations": ops}, **args, ) + + if model is None: + raise RuntimeError("ERROR: Could not detect model type of: {}".format(gguf_full_path)) + + model = GGUFModelPatcher.clone(model) + model.patch_on_device = kwargs.get("patch_on_device", None) + + apply_sampling = kwargs.get("apply_sampling") + if apply_sampling: + args = { + "model": model, + "base_sampling_shift": kwargs.get("base_sampling_shift"), + "max_sampling_shift": kwargs.get("max_sampling_shift"), + "sampling_width": kwargs.get("sampling_width"), + "sampling_height": kwargs.get("sampling_height"), + } + model = patch_flux_sampling(**args) + + clip = load_clip_patcher(kwargs.get("clip"), kwargs.get("clip_type", "flux2")) + + if kwargs.get("apply_bypass_lora"): + loras = [ + (kwargs.get("lora_name_1"), kwargs.get("strength_1"), 'loaded_lora_1'), + (kwargs.get("lora_name_2"), kwargs.get("strength_2"), 'loaded_lora_2'), + (kwargs.get("lora_name_3"), kwargs.get("strength_3"), 'loaded_lora_3'), + (kwargs.get("lora_name_4"), kwargs.get("strength_4"), 'loaded_lora_4'), + ] + + for name, strength, attr in loras: + if name != "None" and strength != 0: + cached = getattr(cls, attr) + model, clip, lora = apply_bypass_lora_for_models(cached, model, clip, name, strength) + setattr(cls, attr, lora) + + vae = load_vae( + kwargs.get("vae_name"), + kwargs.get("vae_device", "default"), + kwargs.get("vae_dtype", "bfloat16"), + ) + + return model, clip, vae + + +class TT_GgufModelsLoaderNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="TT_GgufModelsLoaderNode", + display_name="TT GGUF Models Loader", + category=CATEGORY, + description="", + inputs=[ + io.Combo.Input("diffusion_model", options=get_gguf_diffusion_models_files()), + io.Combo.Input("clip", options=get_gguf_text_encoder_files()), + io.Combo.Input("vae_name", options=get_vae_files()), + ], + outputs=[ + io.Model.Output("MODEL"), + io.Clip.Output("CLIP"), + io.Vae.Output("VAE"), + ] + ) + + @classmethod + def execute(cls, **kwargs) -> io.NodeOutput: + kwargs["apply_bypass_lora"] = False + model, clip, vae = load_gguf_pipeline(cls, apply_loras=True, **kwargs) + + return io.NodeOutput(model, clip, vae) + + +class TT_GgufModelsLoaderAdvancedNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="TT_GgufModelsLoaderAdvancedNode", + display_name="TT GGUF Models Loader (Advanced)", + category=CATEGORY, + description="", + inputs=[ + io.Combo.Input("diffusion_model", options=get_gguf_diffusion_models_files()), + io.Combo.Input("dequant_dtype", options=list(DTYPES.keys()), default="bfloat16", advanced=True), + io.Combo.Input("patch_dtype", options=list(DTYPES.keys()), default="bfloat16", advanced=True), + io.Boolean.Input("apply_sampling", default=True, label_on="Flux Shift", label_off="No Shift"), + io.Float.Input("base_sampling_shift", default=0.5, min=0.0, max=100.0, step=0.01, advanced=True), + io.Float.Input("max_sampling_shift", default=1.15, min=0.0, max=100.0, step=0.01, advanced=True), + io.Int.Input("sampling_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True), + io.Int.Input("sampling_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True), + io.Combo.Input("clip", options=get_gguf_text_encoder_files()), + io.Combo.Input("clip_device", options=TORCH_DEVICES, default="default", advanced=True), + io.Combo.Input("lora_name_1", options=get_lora_files()), + io.Float.Input("strength_1", default=1.0, min=-10.0, max=10.0, step=0.1), + io.Combo.Input("lora_name_2", options=get_lora_files()), + io.Float.Input("strength_2", default=1.0, min=-10.0, max=10.0, step=0.1), + io.Combo.Input("lora_name_3", options=get_lora_files()), + io.Float.Input("strength_3", default=1.0, min=-10.0, max=10.0, step=0.1), + io.Combo.Input("lora_name_4", options=get_lora_files()), + io.Float.Input("strength_4", default=1.0, min=-10.0, max=10.0, step=0.1), + io.Combo.Input("vae_name", options=get_vae_files()), + io.Combo.Input("vae_device", options=TORCH_DEVICES, default="default", advanced=True), + io.Combo.Input("vae_dtype", options=list(DTYPES.keys()), advanced=True), + ], + outputs=[ + io.Model.Output("MODEL"), + io.Clip.Output("CLIP"), + io.Vae.Output("VAE"), + ] + ) + + @classmethod + def execute(cls, **kwargs) -> io.NodeOutput: + kwargs["apply_bypass_lora"] = False + model, clip, vae = load_gguf_pipeline(cls, apply_loras=True, **kwargs) + + return io.NodeOutput(model, clip, vae) + + # ============================================================================== # V3 entrypoint — registers context nodes with ComfyUI # ============================================================================== @@ -392,6 +669,8 @@ class LoaderNodesExtension(ComfyExtension): TT_SdxlModelsLoaderAdvancedNode, TT_FluxModelsLoaderNode, TT_FluxModelsLoaderAdvancedNode, + TT_GgufModelsLoaderNode, + TT_GgufModelsLoaderAdvancedNode, ] @@ -408,4 +687,6 @@ __all__ = [ "TT_SdxlModelsLoaderAdvancedNode", "TT_FluxModelsLoaderNode", "TT_FluxModelsLoaderAdvancedNode", + "TT_GgufModelsLoaderNode", + "TT_GgufModelsLoaderAdvancedNode", ] diff --git a/nodes_workflow.py b/nodes_workflow.py index 994d4c5..5529c18 100644 --- a/nodes_workflow.py +++ b/nodes_workflow.py @@ -11,6 +11,8 @@ from comfy.samplers import KSampler, sampler_object, Sampler from comfy_api.latest import io, ComfyExtension from nodes import MAX_RESOLUTION +CATEGORY = "TenserTensor/Workflow" + @dataclass class TTWorkflowSettings(): @@ -128,7 +130,7 @@ class TT_Flux2WorkflowSettingsNode(io.ComfyNode): return io.Schema( node_id="TT_Flux2WorkflowSettingsNode", display_name="TT FLUX2 Workflow Settings", - category="TenserTensor/Workflow", + category=CATEGORY, description="", inputs=[ io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), @@ -190,7 +192,7 @@ class TT_Flux2WorkflowSettingsAdvancedNode(io.ComfyNode): return io.Schema( node_id="TT_Flux2WorkflowSettingsAdvancedNode", display_name="TT FLUX2 Workflow Settings (Advanced)", - category="TenserTensor/Workflow", + category=CATEGORY, description="", inputs=[ io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), diff --git a/web/docs/TT_FluxModelsLoaderAdvancedNode.md b/web/docs/TT_FluxModelsLoaderAdvancedNode.md new file mode 100644 index 0000000..8600f3b --- /dev/null +++ b/web/docs/TT_FluxModelsLoaderAdvancedNode.md @@ -0,0 +1,49 @@ +## TT FLUX Models Loader (Advanced) *TT_FluxModelsLoaderAdvancedNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Extended FLUX pipeline loader with sampling shift control, up to 4 LoRA slots, and advanced device/dtype configuration. + +### Inputs + +| Parameter | Type | Required | Description | +|---------------------|---------|----------|--------------------------------------------------------------------------------------------------| +| `diffusion_model` | COMBO | Yes | FLUX diffusion model checkpoint file | +| `apply_sampling` | BOOLEAN | Yes | Enable resolution-based sampling shift ("Flux Shift" / "No Shift", default: enabled) | +| `base_sampling_shift` | FLOAT | Yes | Base shift value for sampling schedule (0.0–100.0, default: 0.5) *(advanced)* | +| `max_sampling_shift` | FLOAT | Yes | Maximum shift value for sampling schedule (0.0–100.0, default: 1.15) *(advanced)* | +| `sampling_width` | INT | Yes | Reference width for shift calculation (16–MAX, step: 8, default: 1024) *(advanced)* | +| `sampling_height` | INT | Yes | Reference height for shift calculation (16–MAX, step: 8, default: 1024) *(advanced)* | +| `clip_l` | COMBO | Yes | CLIP-L text encoder file | +| `t5xxl` | COMBO | Yes | T5-XXL text encoder file | +| `clip_device` | COMBO | Yes | Device for CLIP/T5 inference (default: "default") *(advanced)* | +| `lora_name_1` | COMBO | Yes | LoRA file for slot 1 | +| `strength_1` | FLOAT | Yes | Strength for LoRA slot 1 (-10.0–10.0, default: 1.0) | +| `lora_name_2` | COMBO | Yes | LoRA file for slot 2 | +| `strength_2` | FLOAT | Yes | Strength for LoRA slot 2 (-10.0–10.0, default: 1.0) | +| `lora_name_3` | COMBO | Yes | LoRA file for slot 3 | +| `strength_3` | FLOAT | Yes | Strength for LoRA slot 3 (-10.0–10.0, default: 1.0) | +| `lora_name_4` | COMBO | Yes | LoRA file for slot 4 | +| `strength_4` | FLOAT | Yes | Strength for LoRA slot 4 (-10.0–10.0, default: 1.0) | +| `vae_name` | COMBO | Yes | VAE model file for image encoding/decoding | +| `vae_device` | COMBO | Yes | Device for VAE inference (default: "default") *(advanced)* | +| `vae_dtype` | COMBO | Yes | Data type for VAE computation *(advanced)* | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|----------------------------------| +| `MODEL` | MODEL | Loaded FLUX diffusion model | +| `CLIP` | CLIP | Loaded CLIP+T5 text encoder | +| `VAE` | VAE | Loaded VAE | + +### Usage + +Advanced version of the FLUX pipeline loader with full control over all loading parameters. Supports up to +4 simultaneous LoRAs applied at load time. Enable "Flux Shift" to apply resolution-aware sampling shift — +adjust `base_sampling_shift`, `max_sampling_shift`, and the reference resolution to tune the shift curve. +Use advanced device/dtype options to offload components to specific devices or reduce memory usage. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_FluxModelsLoaderNode.md b/web/docs/TT_FluxModelsLoaderNode.md new file mode 100644 index 0000000..eefca17 --- /dev/null +++ b/web/docs/TT_FluxModelsLoaderNode.md @@ -0,0 +1,33 @@ +## TT FLUX Models Loader *TT_FluxModelsLoaderNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Loads all components of the FLUX pipeline (diffusion model, text encoders, and VAE) in a single node. + +### Inputs + +| Parameter | Type | Required | Description | +|-------------------|-------|----------|------------------------------------------------| +| `diffusion_model` | COMBO | Yes | FLUX diffusion model checkpoint file | +| `clip_l` | COMBO | Yes | CLIP-L text encoder file | +| `t5xxl` | COMBO | Yes | T5-XXL text encoder file | +| `clip_device` | COMBO | Yes | Device for CLIP inference (default: "default") | +| `vae_name` | COMBO | Yes | VAE model file for image encoding/decoding | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|-----------------------------| +| `MODEL` | MODEL | Loaded FLUX diffusion model | +| `CLIP` | CLIP | Loaded CLIP+T5 text encoder | +| `VAE` | VAE | Loaded VAE | + +### Usage + +Convenience node for loading the complete FLUX pipeline in one step. Select the diffusion model, +both text encoders (CLIP-L and T5-XXL), and VAE from their respective dropdowns. Use `clip_device` +to offload text encoders to a specific device if needed. LoRA loading is not applied at this stage. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_GgufModelsLoaderAdvancedNode.md b/web/docs/TT_GgufModelsLoaderAdvancedNode.md new file mode 100644 index 0000000..c6219d1 --- /dev/null +++ b/web/docs/TT_GgufModelsLoaderAdvancedNode.md @@ -0,0 +1,51 @@ +## TT GGUF Models Loader (Advanced) *TT_GgufModelsLoaderAdvancedNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Extended GGUF pipeline loader with dequantization control, sampling shift, up to 4 LoRA slots, and advanced device/dtype configuration. + +### Inputs + +| Parameter | Type | Required | Description | +|-----------------------|---------|----------|--------------------------------------------------------------------------------------| +| `diffusion_model` | COMBO | Yes | GGUF-quantized diffusion model file | +| `dequant_dtype` | COMBO | Yes | Data type for dequantization (default: "bfloat16") *(advanced)* | +| `patch_dtype` | COMBO | Yes | Data type for model patches (default: "bfloat16") *(advanced)* | +| `apply_sampling` | BOOLEAN | Yes | Enable resolution-based sampling shift ("Flux Shift" / "No Shift", default: enabled) | +| `base_sampling_shift` | FLOAT | Yes | Base shift value for sampling schedule (0.0–100.0, default: 0.5) *(advanced)* | +| `max_sampling_shift` | FLOAT | Yes | Maximum shift value for sampling schedule (0.0–100.0, default: 1.15) *(advanced)* | +| `sampling_width` | INT | Yes | Reference width for shift calculation (16–MAX, step: 8, default: 1024) *(advanced)* | +| `sampling_height` | INT | Yes | Reference height for shift calculation (16–MAX, step: 8, default: 1024) *(advanced)* | +| `clip` | COMBO | Yes | GGUF-quantized text encoder file | +| `clip_device` | COMBO | Yes | Device for CLIP inference (default: "default") *(advanced)* | +| `lora_name_1` | COMBO | Yes | LoRA file for slot 1 | +| `strength_1` | FLOAT | Yes | Strength for LoRA slot 1 (-10.0–10.0, default: 1.0) | +| `lora_name_2` | COMBO | Yes | LoRA file for slot 2 | +| `strength_2` | FLOAT | Yes | Strength for LoRA slot 2 (-10.0–10.0, default: 1.0) | +| `lora_name_3` | COMBO | Yes | LoRA file for slot 3 | +| `strength_3` | FLOAT | Yes | Strength for LoRA slot 3 (-10.0–10.0, default: 1.0) | +| `lora_name_4` | COMBO | Yes | LoRA file for slot 4 | +| `strength_4` | FLOAT | Yes | Strength for LoRA slot 4 (-10.0–10.0, default: 1.0) | +| `vae_name` | COMBO | Yes | VAE model file for image encoding/decoding | +| `vae_device` | COMBO | Yes | Device for VAE inference (default: "default") *(advanced)* | +| `vae_dtype` | COMBO | Yes | Data type for VAE computation *(advanced)* | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|-----------------------------| +| `MODEL` | MODEL | Loaded GGUF diffusion model | +| `CLIP` | CLIP | Loaded GGUF text encoder | +| `VAE` | VAE | Loaded VAE | + +### Usage + +Advanced GGUF pipeline loader with full control over quantization and loading parameters. Use `dequant_dtype` +and `patch_dtype` to balance precision and VRAM usage during dequantization. Supports up to 4 simultaneous +LoRAs applied at load time. Enable "Flux Shift" to apply resolution-aware sampling shift — adjust +`base_sampling_shift`, `max_sampling_shift`, and the reference resolution to tune the shift curve. +Use advanced device options to offload components as needed. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) \ No newline at end of file diff --git a/web/docs/TT_GgufModelsLoaderNode.md b/web/docs/TT_GgufModelsLoaderNode.md new file mode 100644 index 0000000..9626aa4 --- /dev/null +++ b/web/docs/TT_GgufModelsLoaderNode.md @@ -0,0 +1,31 @@ +## TT GGUF Models Loader *TT_GgufModelsLoaderNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Loads a complete GGUF-quantized pipeline (diffusion model, text encoder, and VAE) in a single node. + +### Inputs + +| Parameter | Type | Required | Description | +|-------------------|-------|----------|---------------------------------------------| +| `diffusion_model` | COMBO | Yes | GGUF-quantized diffusion model file | +| `clip` | COMBO | Yes | GGUF-quantized text encoder file | +| `vae_name` | COMBO | Yes | VAE model file for image encoding/decoding | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|-------------------------------------| +| `MODEL` | MODEL | Loaded GGUF diffusion model | +| `CLIP` | CLIP | Loaded GGUF text encoder | +| `VAE` | VAE | Loaded VAE | + +### Usage + +Convenience node for loading a GGUF-quantized pipeline in one step. Designed for use with quantized model +files that reduce VRAM requirements. Select the GGUF diffusion model, GGUF text encoder, and VAE from +their respective dropdowns. LoRA loading is supported at this stage; bypass LoRA is not applied. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)