Files
2026-10-06 06:22:21 -04:00

248 lines
6.8 KiB
Python

import os
import comfy.sd
import comfy.utils
import folder_paths
from .isolation import Krea2TextPathDeltaIsolation
BRANCH_PROJECTION = "projection"
BRANCH_TEXTFUSION = "textfusion"
BRANCH_TXTMLP = "txtmlp"
BRANCH_OTHER = "other"
BRANCH_ORDER = (
BRANCH_PROJECTION,
BRANCH_TEXTFUSION,
BRANCH_TXTMLP,
BRANCH_OTHER,
)
def _normalized_key(key):
return "." + key.lower().replace("_", ".").strip(".") + "."
def _contains_any(key, module_names):
normalized = _normalized_key(key)
return any(f".{module_name}." in normalized for module_name in module_names)
def _classify_key(key):
if _contains_any(
key,
(
"txtfusion.projector",
"textfusion.projector",
"text.fusion.projector",
),
):
return BRANCH_PROJECTION
if _contains_any(key, ("txtfusion", "textfusion", "text.fusion")):
return BRANCH_TEXTFUSION
if _contains_any(
key,
(
"txtmlp",
"txt.mlp",
"txt.in.linear.1",
"txt.in.linear.2",
),
):
return BRANCH_TXTMLP
return BRANCH_OTHER
def _branch_counts(lora):
counts = {branch: 0 for branch in BRANCH_ORDER}
for key in lora:
counts[_classify_key(key)] += 1
return counts
def _filter_lora(lora, load_projection, load_textfusion, load_txtmlp):
enabled = {
BRANCH_PROJECTION: bool(load_projection),
BRANCH_TEXTFUSION: bool(load_textfusion),
BRANCH_TXTMLP: bool(load_txtmlp),
BRANCH_OTHER: True,
}
kept = {
key: value
for key, value in lora.items()
if enabled[_classify_key(key)]
}
return kept, _branch_counts(lora), _branch_counts(kept)
def _file_signature(lora_name):
path = folder_paths.get_full_path_or_raise("loras", lora_name)
stat = os.stat(path)
return (
path,
stat.st_ino,
stat.st_size,
stat.st_mtime_ns,
stat.st_ctime_ns,
)
def _format_counts(counts):
return ",".join(f"{branch}:{counts[branch]}" for branch in BRANCH_ORDER)
class Krea2SelectiveLoraLoaderModelOnlyProjection:
def __init__(self):
self._cache_signature = None
self._cache_lora = None
self._cache_metadata = None
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"lora_name": (
folder_paths.get_filename_list("loras"),
{"tooltip": "LoRA applied only to the diffusion model."},
),
"strength_model": (
"FLOAT",
{
"default": 1.0,
"min": -100.0,
"max": 100.0,
"step": 0.01,
},
),
"load_projection": (
"BOOLEAN",
{
"default": True,
"tooltip": "Load only txtfusion.projector LoRA tensors.",
},
),
"load_textfusion": (
"BOOLEAN",
{
"default": True,
"tooltip": "Load txtfusion layerwise/refiner tensors, excluding the projector.",
},
),
"load_txtmlp": (
"BOOLEAN",
{
"default": True,
"tooltip": "Load txtmlp linear-layer LoRA tensors.",
},
),
"debug": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("MODEL", "STRING")
RETURN_NAMES = ("model", "report")
FUNCTION = "load_lora_model_only"
CATEGORY = "Krea2/loaders"
DESCRIPTION = (
"Loads a model-only LoRA with independent switches for the TextFusion "
"projector, the remaining TextFusion blocks, and txtmlp."
)
@classmethod
def IS_CHANGED(
cls,
model,
lora_name,
strength_model,
load_projection,
load_textfusion,
load_txtmlp,
debug,
):
return _file_signature(lora_name)
def _load_raw_lora(self, lora_name):
signature = _file_signature(lora_name)
if self._cache_signature != signature:
lora, metadata = comfy.utils.load_torch_file(
signature[0],
safe_load=True,
return_metadata=True,
)
self._cache_signature = signature
self._cache_lora = lora
self._cache_metadata = metadata
return signature[0], self._cache_lora, self._cache_metadata
def load_lora_model_only(
self,
model,
lora_name,
strength_model,
load_projection,
load_textfusion,
load_txtmlp,
debug,
):
if strength_model == 0:
report = "Krea2 Projection-Split LoRA: bypassed because strength_model=0"
if debug:
print(report)
return model, report
lora_path, raw_lora, metadata = self._load_raw_lora(lora_name)
filtered_lora, source_counts, kept_counts = _filter_lora(
raw_lora,
load_projection=load_projection,
load_textfusion=load_textfusion,
load_txtmlp=load_txtmlp,
)
if filtered_lora:
model_lora, _ = comfy.sd.load_lora_for_models(
model,
None,
filtered_lora,
strength_model,
0.0,
lora_metadata=metadata,
)
else:
model_lora = model
report = (
"Krea2 Projection-Split LoRA: "
f"lora={lora_name}; "
f"kept={len(filtered_lora)}/{len(raw_lora)}; "
f"source=[{_format_counts(source_counts)}]; "
f"loaded=[{_format_counts(kept_counts)}]; "
f"load_projection={bool(load_projection)}; "
f"load_textfusion={bool(load_textfusion)}; "
f"load_txtmlp={bool(load_txtmlp)}; "
f"strength_model={strength_model:.4g}; "
f"path={lora_path}"
)
if debug:
print(report)
return model_lora, report
NODE_CLASS_MAPPINGS = {
"Krea2SelectiveLoraLoaderModelOnlyProjection": Krea2SelectiveLoraLoaderModelOnlyProjection,
"Krea2TextPathDeltaIsolation": Krea2TextPathDeltaIsolation,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Krea2TextPathDeltaIsolation": "Krea2 · Projector + External MLP · LoRA Isolation",
"Krea2SelectiveLoraLoaderModelOnlyProjection": (
"Krea2 Selective LoRA Loader Model Only (Projection Split)"
),
}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]