Image-to-Video
Diffusers
text-to-video
video-to-video
image-text-to-video
audio-to-video
text-to-audio
video-to-audio
audio-to-audio
text-to-audio-video
image-to-audio-video
image-text-to-audio-video
ltx-2
ltx-2-3
ltx-video
ltxv
lightricks
nvfp4
quantized
fp4me
Instructions to use MrReclusive/LTX-2.3-FP4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use MrReclusive/LTX-2.3-FP4 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image, export_to_video # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("MrReclusive/LTX-2.3-FP4", dtype=torch.bfloat16, device_map="cuda") pipe.to("cuda") prompt = "A man with short gray hair plays a red electric guitar." image = load_image( "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/guitar-man.png" ) output = pipe(image=image, prompt=prompt).frames[0] export_to_video(output, "output.mp4") - Notebooks
- Google Colab
- Kaggle
Upload comfy_bathroom.py
Browse files- comfy_bathroom.py +406 -0
comfy_bathroom.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Comfy Bathroom - LoRA Loading Suite for FP4 Quantized Models
|
| 3 |
+
|
| 4 |
+
A complete LoRA loading system designed for use with FP4ME/F4PMEL quantized LTX-2.3 models.
|
| 5 |
+
|
| 6 |
+
Author: Super Z
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import comfy.utils
|
| 10 |
+
import folder_paths
|
| 11 |
+
import torch
|
| 12 |
+
import re
|
| 13 |
+
from typing import Dict, List, Optional, Tuple, Any
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# =============================================================================
|
| 17 |
+
# PRESET CURVES
|
| 18 |
+
# =============================================================================
|
| 19 |
+
|
| 20 |
+
def generate_ramp_up(start_block: int, end_block: int, start_val: float, end_val: float) -> Dict[int, float]:
|
| 21 |
+
"""Generate a smooth ramp between two blocks."""
|
| 22 |
+
curve = {}
|
| 23 |
+
if end_block <= start_block:
|
| 24 |
+
return curve
|
| 25 |
+
steps = end_block - start_block
|
| 26 |
+
for i, block in enumerate(range(start_block, end_block + 1)):
|
| 27 |
+
t = i / steps
|
| 28 |
+
curve[block] = start_val + (end_val - start_val) * t
|
| 29 |
+
return curve
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def get_fp4me_light_weights() -> Dict[int, float]:
|
| 33 |
+
"""FP4ME Light preset."""
|
| 34 |
+
weights = {}
|
| 35 |
+
weights[0] = 1.0
|
| 36 |
+
weights[1] = 0.0
|
| 37 |
+
weights.update(generate_ramp_up(2, 10, 0.10, 1.0))
|
| 38 |
+
for i in range(11, 40):
|
| 39 |
+
weights[i] = 1.0
|
| 40 |
+
weights.update(generate_ramp_up(40, 46, 0.95, 0.50))
|
| 41 |
+
weights[47] = 1.0
|
| 42 |
+
return weights
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_fp4me_heavy_weights() -> Dict[int, float]:
|
| 46 |
+
"""FP4ME Heavy preset."""
|
| 47 |
+
weights = {}
|
| 48 |
+
weights[0] = 1.0
|
| 49 |
+
weights[1] = 0.0
|
| 50 |
+
weights.update(generate_ramp_up(2, 10, 0.10, 1.0))
|
| 51 |
+
for i in range(11, 40):
|
| 52 |
+
weights[i] = 1.0
|
| 53 |
+
weights.update(generate_ramp_up(40, 46, 1.0, 0.0))
|
| 54 |
+
weights[47] = 1.0
|
| 55 |
+
return weights
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_fp4mel_light_weights() -> Dict[int, float]:
|
| 59 |
+
"""FP4MEL Light preset."""
|
| 60 |
+
weights = {}
|
| 61 |
+
weights[0] = 1.0
|
| 62 |
+
weights[1] = 1.0
|
| 63 |
+
weights.update(generate_ramp_up(2, 10, 0.10, 1.0))
|
| 64 |
+
for i in range(11, 41):
|
| 65 |
+
weights[i] = 1.0
|
| 66 |
+
weights.update(generate_ramp_up(41, 45, 0.95, 0.60))
|
| 67 |
+
weights[46] = 1.0
|
| 68 |
+
weights[47] = 1.0
|
| 69 |
+
return weights
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def get_fp4mel_heavy_weights() -> Dict[int, float]:
|
| 73 |
+
"""FP4MEL Heavy preset."""
|
| 74 |
+
weights = {}
|
| 75 |
+
weights[0] = 1.0
|
| 76 |
+
weights[1] = 1.0
|
| 77 |
+
weights.update(generate_ramp_up(2, 10, 0.10, 1.0))
|
| 78 |
+
for i in range(11, 40):
|
| 79 |
+
weights[i] = 1.0
|
| 80 |
+
weights.update(generate_ramp_up(40, 45, 0.95, 0.0))
|
| 81 |
+
weights[46] = 1.0
|
| 82 |
+
weights[47] = 1.0
|
| 83 |
+
return weights
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def apply_block_weights_to_lora(lora_data: dict, block_weights: Dict[int, float]) -> dict:
|
| 87 |
+
"""Apply per-block weights to LoRA data."""
|
| 88 |
+
filtered = {}
|
| 89 |
+
for key, value in lora_data.items():
|
| 90 |
+
block_match = re.search(r'transformer_blocks\.(\d+)\.', key)
|
| 91 |
+
if block_match:
|
| 92 |
+
block_idx = int(block_match.group(1))
|
| 93 |
+
weight = block_weights.get(block_idx, 1.0)
|
| 94 |
+
if weight > 0.0:
|
| 95 |
+
filtered[key] = value * weight if weight < 1.0 else value
|
| 96 |
+
else:
|
| 97 |
+
filtered[key] = value
|
| 98 |
+
return filtered
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# =============================================================================
|
| 102 |
+
# TOOTHBRUSH - LoRA Loader
|
| 103 |
+
# =============================================================================
|
| 104 |
+
|
| 105 |
+
class ToothbrushLoRALoader:
|
| 106 |
+
PRESET_OPTIONS = ["default", "FP4ME Light", "FP4ME Heavy", "FP4MEL Light", "FP4MEL Heavy", "Custom"]
|
| 107 |
+
|
| 108 |
+
@classmethod
|
| 109 |
+
def INPUT_TYPES(s):
|
| 110 |
+
return {
|
| 111 |
+
"required": {
|
| 112 |
+
"lora_name": (folder_paths.get_filename_list("loras"),),
|
| 113 |
+
"preset": (s.PRESET_OPTIONS, {"default": "default"}),
|
| 114 |
+
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05}),
|
| 115 |
+
},
|
| 116 |
+
"optional": {
|
| 117 |
+
"custom_weights": ("LORA_BLOCK_WEIGHTS",),
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
RETURN_TYPES = ("LORA_PACKET",)
|
| 122 |
+
RETURN_NAMES = ("lora",)
|
| 123 |
+
FUNCTION = "load_lora"
|
| 124 |
+
CATEGORY = "bathroom"
|
| 125 |
+
DESCRIPTION = "🪥 Toothbrush - LoRA loader with FP4 presets"
|
| 126 |
+
|
| 127 |
+
def load_lora(self, lora_name, preset, strength, custom_weights=None):
|
| 128 |
+
lora_path = folder_paths.get_full_path("loras", lora_name)
|
| 129 |
+
lora_data = comfy.utils.load_torch_file(lora_path, safe_load=False)
|
| 130 |
+
|
| 131 |
+
block_weights = None
|
| 132 |
+
if preset == "FP4ME Light":
|
| 133 |
+
block_weights = get_fp4me_light_weights()
|
| 134 |
+
elif preset == "FP4ME Heavy":
|
| 135 |
+
block_weights = get_fp4me_heavy_weights()
|
| 136 |
+
elif preset == "FP4MEL Light":
|
| 137 |
+
block_weights = get_fp4mel_light_weights()
|
| 138 |
+
elif preset == "FP4MEL Heavy":
|
| 139 |
+
block_weights = get_fp4mel_heavy_weights()
|
| 140 |
+
elif preset == "Custom":
|
| 141 |
+
block_weights = custom_weights
|
| 142 |
+
|
| 143 |
+
packet = {
|
| 144 |
+
"lora_data": lora_data,
|
| 145 |
+
"preset": preset,
|
| 146 |
+
"strength": strength,
|
| 147 |
+
"block_weights": block_weights,
|
| 148 |
+
"lora_name": lora_name,
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
print(f"🪥 Toothbrush: '{lora_name}' | {preset} | {strength:.2f}")
|
| 152 |
+
return (packet,)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# =============================================================================
|
| 156 |
+
# MIRROR SIMPLE - Binary On/Off
|
| 157 |
+
# =============================================================================
|
| 158 |
+
|
| 159 |
+
class MirrorSimple:
|
| 160 |
+
@classmethod
|
| 161 |
+
def INPUT_TYPES(s):
|
| 162 |
+
block_inputs = {f"block_{i}": ("BOOLEAN", {"default": True}) for i in range(48)}
|
| 163 |
+
return {
|
| 164 |
+
"required": block_inputs,
|
| 165 |
+
"optional": {"lora_packet": ("LORA_PACKET",)}
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
RETURN_TYPES = ("LORA_BLOCK_WEIGHTS", "LORA_PACKET")
|
| 169 |
+
RETURN_NAMES = ("block_weights", "lora_out")
|
| 170 |
+
FUNCTION = "configure"
|
| 171 |
+
CATEGORY = "bathroom"
|
| 172 |
+
DESCRIPTION = "🪞 Mirror (Simple) - Per-block on/off"
|
| 173 |
+
|
| 174 |
+
def configure(self, lora_packet=None, **kwargs):
|
| 175 |
+
block_weights = {i: (1.0 if kwargs.get(f"block_{i}", True) else 0.0) for i in range(48)}
|
| 176 |
+
disabled = [i for i, w in block_weights.items() if w == 0.0]
|
| 177 |
+
print(f"🪞 Mirror (Simple): {48-len(disabled)} ON, {len(disabled)} OFF")
|
| 178 |
+
|
| 179 |
+
out_packet = lora_packet.copy() if lora_packet else None
|
| 180 |
+
if out_packet:
|
| 181 |
+
out_packet["block_weights"] = block_weights
|
| 182 |
+
return (block_weights, out_packet)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# =============================================================================
|
| 186 |
+
# MIRROR FANCY - Per-Block Strength
|
| 187 |
+
# =============================================================================
|
| 188 |
+
|
| 189 |
+
class MirrorFancy:
|
| 190 |
+
@classmethod
|
| 191 |
+
def INPUT_TYPES(s):
|
| 192 |
+
block_inputs = {f"block_{i}": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05}) for i in range(48)}
|
| 193 |
+
return {
|
| 194 |
+
"required": block_inputs,
|
| 195 |
+
"optional": {"lora_packet": ("LORA_PACKET",)}
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
RETURN_TYPES = ("LORA_BLOCK_WEIGHTS", "LORA_PACKET")
|
| 199 |
+
RETURN_NAMES = ("block_weights", "lora_out")
|
| 200 |
+
FUNCTION = "configure"
|
| 201 |
+
CATEGORY = "bathroom"
|
| 202 |
+
DESCRIPTION = "🪞 Mirror (Fancy) - Per-block strength"
|
| 203 |
+
|
| 204 |
+
def configure(self, lora_packet=None, **kwargs):
|
| 205 |
+
block_weights = {i: kwargs.get(f"block_{i}", 1.0) for i in range(48)}
|
| 206 |
+
active = sum(1 for w in block_weights.values() if w > 0)
|
| 207 |
+
print(f"🪞 Mirror (Fancy): {active} blocks active")
|
| 208 |
+
|
| 209 |
+
out_packet = lora_packet.copy() if lora_packet else None
|
| 210 |
+
if out_packet:
|
| 211 |
+
out_packet["block_weights"] = block_weights
|
| 212 |
+
return (block_weights, out_packet)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
# =============================================================================
|
| 216 |
+
# BATHROOM SINK - LoRA Stacker
|
| 217 |
+
# =============================================================================
|
| 218 |
+
|
| 219 |
+
class BathroomSink:
|
| 220 |
+
@classmethod
|
| 221 |
+
def INPUT_TYPES(s):
|
| 222 |
+
return {
|
| 223 |
+
"required": {
|
| 224 |
+
"model": ("MODEL",),
|
| 225 |
+
"lora_1": ("LORA_PACKET",),
|
| 226 |
+
"global_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05}),
|
| 227 |
+
},
|
| 228 |
+
"optional": {f"lora_{i}": ("LORA_PACKET",) for i in range(2, 9)}
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
RETURN_TYPES = ("MODEL",)
|
| 232 |
+
RETURN_NAMES = ("model",)
|
| 233 |
+
FUNCTION = "apply_loras"
|
| 234 |
+
CATEGORY = "bathroom"
|
| 235 |
+
DESCRIPTION = "🚰 Bathroom Sink - Stack multiple LoRAs"
|
| 236 |
+
|
| 237 |
+
def apply_loras(self, model, lora_1, global_strength, **kwargs):
|
| 238 |
+
lora_packets = [lora_1] + [kwargs.get(f"lora_{i}") for i in range(2, 9) if kwargs.get(f"lora_{i}")]
|
| 239 |
+
|
| 240 |
+
print(f"\n{'='*60}")
|
| 241 |
+
print(f"🚰 Bathroom Sink - {len(lora_packets)} LoRAs, global: {global_strength:.2f}")
|
| 242 |
+
print(f"{'='*60}")
|
| 243 |
+
|
| 244 |
+
model_out = model.clone()
|
| 245 |
+
|
| 246 |
+
for idx, packet in enumerate(lora_packets):
|
| 247 |
+
lora_data = packet["lora_data"]
|
| 248 |
+
strength = packet["strength"] * global_strength
|
| 249 |
+
block_weights = packet.get("block_weights")
|
| 250 |
+
lora_name = packet.get("lora_name", f"LoRA_{idx+1}")
|
| 251 |
+
preset = packet.get("preset", "default")
|
| 252 |
+
|
| 253 |
+
if block_weights:
|
| 254 |
+
processed_data = apply_block_weights_to_lora(lora_data, block_weights)
|
| 255 |
+
else:
|
| 256 |
+
processed_data = lora_data
|
| 257 |
+
|
| 258 |
+
print(f" [{idx+1}] {lora_name} | {preset} | {strength:.2f}")
|
| 259 |
+
|
| 260 |
+
# Apply using ComfyUI's standard LoRA mechanism
|
| 261 |
+
key_map = comfy.lora.model_lora_keys_unet(model_out.model)
|
| 262 |
+
|
| 263 |
+
try:
|
| 264 |
+
# Try loading - handle both old and new ComfyUI API
|
| 265 |
+
result = comfy.lora.load_lora(processed_data, key_map)
|
| 266 |
+
|
| 267 |
+
# Check if result is the new LoRAAdapter format
|
| 268 |
+
if hasattr(result, 'patches'):
|
| 269 |
+
# New API - LoRAAdapter object
|
| 270 |
+
model_out.add_patches(result.patches, strength)
|
| 271 |
+
elif isinstance(result, dict):
|
| 272 |
+
# Old API - patch dict
|
| 273 |
+
model_out.add_patches(result, strength)
|
| 274 |
+
else:
|
| 275 |
+
# Try to apply directly
|
| 276 |
+
model_out.add_patches(result, strength)
|
| 277 |
+
|
| 278 |
+
except Exception as e:
|
| 279 |
+
print(f" ⚠️ LoRA load error: {e}")
|
| 280 |
+
# Fallback: use the original approach
|
| 281 |
+
try:
|
| 282 |
+
# Build patches manually
|
| 283 |
+
patches = self._build_patches(processed_data, key_map)
|
| 284 |
+
if patches:
|
| 285 |
+
model_out.add_patches(patches, strength)
|
| 286 |
+
except Exception as e2:
|
| 287 |
+
print(f" ⚠️ Fallback failed: {e2}")
|
| 288 |
+
|
| 289 |
+
print(f"{'='*60}\n")
|
| 290 |
+
return (model_out,)
|
| 291 |
+
|
| 292 |
+
def _build_patches(self, lora_data, key_map):
|
| 293 |
+
"""Build patch dict manually."""
|
| 294 |
+
patches = {}
|
| 295 |
+
|
| 296 |
+
for lora_key, lora_value in lora_data.items():
|
| 297 |
+
# Find the model key
|
| 298 |
+
model_key = key_map.get(lora_key, None)
|
| 299 |
+
if model_key is None:
|
| 300 |
+
continue
|
| 301 |
+
|
| 302 |
+
if model_key not in patches:
|
| 303 |
+
patches[model_key] = []
|
| 304 |
+
|
| 305 |
+
# Add as a diff patch
|
| 306 |
+
if ".lora_A.weight" in lora_key:
|
| 307 |
+
# Find the matching lora_B
|
| 308 |
+
b_key = lora_key.replace(".lora_A.weight", ".lora_B.weight")
|
| 309 |
+
if b_key in lora_data:
|
| 310 |
+
lora_b = lora_data[b_key]
|
| 311 |
+
# Compute delta
|
| 312 |
+
if lora_value.dim() == 2 and lora_b.dim() == 2:
|
| 313 |
+
delta = torch.mm(lora_b, lora_value)
|
| 314 |
+
patches[model_key].append(("diff", delta))
|
| 315 |
+
elif ".lora_B.weight" not in lora_key:
|
| 316 |
+
# Direct value (diff format)
|
| 317 |
+
patches[model_key].append(("diff", lora_value))
|
| 318 |
+
|
| 319 |
+
return patches
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
# =============================================================================
|
| 323 |
+
# SHOWER - Quick Preset
|
| 324 |
+
# =============================================================================
|
| 325 |
+
|
| 326 |
+
class ShowerPreset:
|
| 327 |
+
PRESET_OPTIONS = ["FP4ME Light", "FP4ME Heavy", "FP4MEL Light", "FP4MEL Heavy"]
|
| 328 |
+
|
| 329 |
+
@classmethod
|
| 330 |
+
def INPUT_TYPES(s):
|
| 331 |
+
return {
|
| 332 |
+
"required": {
|
| 333 |
+
"lora_packet": ("LORA_PACKET",),
|
| 334 |
+
"preset": (s.PRESET_OPTIONS, {"default": "FP4ME Light"}),
|
| 335 |
+
}
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
RETURN_TYPES = ("LORA_PACKET",)
|
| 339 |
+
RETURN_NAMES = ("lora_out",)
|
| 340 |
+
FUNCTION = "apply_preset"
|
| 341 |
+
CATEGORY = "bathroom"
|
| 342 |
+
DESCRIPTION = "🚿 Shower - Quick preset"
|
| 343 |
+
|
| 344 |
+
def apply_preset(self, lora_packet, preset):
|
| 345 |
+
out = lora_packet.copy()
|
| 346 |
+
presets = {
|
| 347 |
+
"FP4ME Light": get_fp4me_light_weights,
|
| 348 |
+
"FP4ME Heavy": get_fp4me_heavy_weights,
|
| 349 |
+
"FP4MEL Light": get_fp4mel_light_weights,
|
| 350 |
+
"FP4MEL Heavy": get_fp4mel_heavy_weights,
|
| 351 |
+
}
|
| 352 |
+
out["block_weights"] = presets[preset]()
|
| 353 |
+
out["preset"] = preset
|
| 354 |
+
print(f"🚿 Shower: {preset}")
|
| 355 |
+
return (out,)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
# =============================================================================
|
| 359 |
+
# TOWEL - Info Display
|
| 360 |
+
# =============================================================================
|
| 361 |
+
|
| 362 |
+
class TowelInfo:
|
| 363 |
+
@classmethod
|
| 364 |
+
def INPUT_TYPES(s):
|
| 365 |
+
return {"required": {"lora_packet": ("LORA_PACKET",)}}
|
| 366 |
+
|
| 367 |
+
RETURN_TYPES = ("LORA_PACKET", "STRING")
|
| 368 |
+
RETURN_NAMES = ("lora_out", "info")
|
| 369 |
+
FUNCTION = "display_info"
|
| 370 |
+
CATEGORY = "bathroom"
|
| 371 |
+
OUTPUT_NODE = True
|
| 372 |
+
DESCRIPTION = "🧾 Towel - Info"
|
| 373 |
+
|
| 374 |
+
def display_info(self, lora_packet):
|
| 375 |
+
lines = [
|
| 376 |
+
f"LoRA: {lora_packet.get('lora_name', '?')}",
|
| 377 |
+
f"Preset: {lora_packet.get('preset', '?')}",
|
| 378 |
+
f"Strength: {lora_packet.get('strength', 1):.2f}",
|
| 379 |
+
]
|
| 380 |
+
bw = lora_packet.get("block_weights")
|
| 381 |
+
if bw:
|
| 382 |
+
lines.append(f"Disabled: {[i for i,w in bw.items() if w<0.01]}")
|
| 383 |
+
return (lora_packet, "\n".join(lines))
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
# =============================================================================
|
| 387 |
+
# NODE MAPPINGS
|
| 388 |
+
# =============================================================================
|
| 389 |
+
|
| 390 |
+
NODE_CLASS_MAPPINGS = {
|
| 391 |
+
"Toothbrush LoRA Loader": ToothbrushLoRALoader,
|
| 392 |
+
"Mirror (Simple)": MirrorSimple,
|
| 393 |
+
"Mirror (Fancy)": MirrorFancy,
|
| 394 |
+
"Bathroom Sink": BathroomSink,
|
| 395 |
+
"Shower Preset": ShowerPreset,
|
| 396 |
+
"Towel Info": TowelInfo,
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
NODE_DISPLAY_NAME_MAPPINGS = {
|
| 400 |
+
"Toothbrush LoRA Loader": "🪥 Toothbrush",
|
| 401 |
+
"Mirror (Simple)": "🪞 Mirror (Simple)",
|
| 402 |
+
"Mirror (Fancy)": "🪞 Mirror (Fancy)",
|
| 403 |
+
"Bathroom Sink": "🚰 Bathroom Sink",
|
| 404 |
+
"Shower Preset": "🚿 Shower",
|
| 405 |
+
"Towel Info": "🧾 Towel",
|
| 406 |
+
}
|