554 lines
22 KiB
Python
554 lines
22 KiB
Python
"""Qwen TeleStyle Loader: given just an architecture choice (2509/2511) and a device, resolves
|
|
(downloading if missing) the transformer/text_encoder/vae/lora weight files from ComfyUI's
|
|
own model folders, builds the diffusers pipeline once, and hands it to Qwen TeleStyle Apply
|
|
as a single TELESTYLE_MODELS socket - so Apply itself only needs to know about *generation*,
|
|
never about where any file lives.
|
|
|
|
Loading is a single, direct read of each flat safetensors file straight into this pipeline's
|
|
own modules - no ComfyUI MODEL/CLIP/VAE objects are involved, so nothing is ever resident in
|
|
memory twice (see the module docstring in the old comfy_bridge-based approach for why
|
|
bridging *live* comfy objects doubles memory and OOMs on boxes with much less system RAM
|
|
than VRAM, like this one: 62GB RAM vs 97GB VRAM).
|
|
"""
|
|
|
|
import gc
|
|
import json
|
|
import os
|
|
import shutil
|
|
import threading
|
|
from types import SimpleNamespace
|
|
|
|
import torch
|
|
from safetensors.torch import load_file as load_safetensors, save_file as save_safetensors
|
|
|
|
import folder_paths
|
|
from transformers import AutoConfig, Qwen2_5_VLForConditionalGeneration, Qwen2Tokenizer, Qwen2VLProcessor
|
|
from diffusers.models import AutoencoderKLQwenImage, QwenImageTransformer2DModel
|
|
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
|
|
|
|
try:
|
|
from . import vae_convert
|
|
except ImportError:
|
|
import vae_convert
|
|
|
|
try:
|
|
from .pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
|
except ImportError:
|
|
from pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
|
|
|
PIPELINE_DTYPE = torch.bfloat16
|
|
OFFLOAD_MODES = ("keep_loaded", "model_cpu_offload", "sequential_cpu_offload")
|
|
|
|
# text encoder + vae are shared across architectures (same base Qwen-Image-Edit family);
|
|
# only the transformer checkpoint and the two TeleStyleV2 LoRAs are architecture-specific.
|
|
TEXT_ENCODER_FILE = "qwen_2.5_vl_7b.safetensors"
|
|
VAE_FILE = "qwen_image_vae.safetensors"
|
|
|
|
# HF sources used only to auto-download a file ComfyUI doesn't have yet - once downloaded
|
|
# it lives permanently under ComfyUI's own model folders like any other checkpoint.
|
|
TEXT_ENCODER_REPO = "Comfy-Org/HunyuanVideo_1.5_repackaged"
|
|
TEXT_ENCODER_REPO_FILE = "split_files/text_encoders/qwen_2.5_vl_7b.safetensors"
|
|
VAE_REPO = "f5aiteam/VAE"
|
|
VAE_REPO_FILE = "qwen_image_vae.safetensors"
|
|
LORA_REPO = "Tele-AI/TeleStyleV2"
|
|
TRANSFORMER_SUBFOLDER = "transformer"
|
|
|
|
ARCHITECTURES = {
|
|
"2509": {
|
|
"transformer_file": "Qwen-Image-Edit-2509.safetensors",
|
|
# pre-merged single file (this pack's own repo) - no shard download/merge needed.
|
|
"transformer_single_file_repo": "troopa/Qwen-Image-Edit-2509",
|
|
# fallback if the repo above is ever unavailable: merge from the original shards.
|
|
"transformer_sharded_repo": "Qwen/Qwen-Image-Edit-2509",
|
|
"style_lora": "diffusers-TeleStyleV2-QIE-2509-Lora-bf16.safetensors",
|
|
"speed_lora": "QIE-2509-Lightning-4steps-V1.0-bf16.safetensors",
|
|
},
|
|
"2511": {
|
|
"transformer_file": "Qwen-Image-Edit-2511.safetensors",
|
|
"transformer_single_file_repo": "troopa/Qwen-Image-Edit-2511",
|
|
"transformer_sharded_repo": "Qwen/Qwen-Image-Edit-2511",
|
|
"style_lora": "diffusers-TeleStyleV2-QIE-2511-Lora-bf16.safetensors",
|
|
"speed_lora": "QIE-2511-Lightning-4steps-V1.0-bf16.safetensors",
|
|
},
|
|
}
|
|
|
|
# config/tokenizer/processor/scheduler configs only (no weights) - bundled with this node,
|
|
# independent of app.py's project.
|
|
ASSET_MODEL_DIR = os.path.join(os.path.dirname(__file__), "assets", "Qwen-Image-Edit-2509")
|
|
|
|
_PIPELINE_CACHE = {"key": None, "pipe": None}
|
|
_PIPELINE_LOCK = threading.Lock()
|
|
|
|
|
|
def _existing_path(folder_name, filename):
|
|
# folder_paths.get_folder_paths() can list several directories for one folder_name
|
|
# (e.g. a legacy "unet" alias ahead of the real "diffusion_models" dir) - check every
|
|
# registered directory, not just the first, before concluding a file is missing.
|
|
for folder in folder_paths.get_folder_paths(folder_name):
|
|
candidate = os.path.join(folder, filename)
|
|
if os.path.isfile(candidate):
|
|
return candidate
|
|
return None
|
|
|
|
|
|
def _download_target_path(folder_name, filename):
|
|
models_dir = getattr(folder_paths, "models_dir", None)
|
|
if models_dir:
|
|
return os.path.join(models_dir, folder_name, filename)
|
|
return os.path.join(folder_paths.get_folder_paths(folder_name)[-1], filename)
|
|
|
|
|
|
def resolve_device(device):
|
|
if device == "auto":
|
|
return "cuda" if torch.cuda.is_available() else "cpu"
|
|
return device
|
|
|
|
|
|
def _materialize_file(source_path, target_path):
|
|
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
temp_path = f"{target_path}.tmp"
|
|
if os.path.lexists(temp_path):
|
|
os.unlink(temp_path)
|
|
shutil.copy2(source_path, temp_path)
|
|
os.replace(temp_path, target_path)
|
|
|
|
|
|
def _materialize_existing_or_downloaded(existing_path, target_path, repo_id, repo_file):
|
|
if existing_path is not None and not os.path.islink(existing_path):
|
|
if os.path.abspath(existing_path) == os.path.abspath(target_path):
|
|
return existing_path
|
|
_materialize_file(existing_path, target_path)
|
|
return target_path
|
|
|
|
from huggingface_hub import hf_hub_download
|
|
|
|
downloaded = hf_hub_download(repo_id=repo_id, filename=repo_file)
|
|
destination = existing_path or target_path
|
|
if os.path.islink(destination):
|
|
os.unlink(destination)
|
|
_materialize_file(downloaded, destination)
|
|
return destination
|
|
|
|
|
|
def _ensure_single_file(folder_name, filename, repo_id, repo_file):
|
|
existing = _existing_path(folder_name, filename)
|
|
target_path = _download_target_path(folder_name, filename)
|
|
return _materialize_existing_or_downloaded(existing, target_path, repo_id, repo_file)
|
|
|
|
|
|
def _ensure_lora(filename, repo_file):
|
|
return _ensure_single_file("loras", filename, LORA_REPO, repo_file)
|
|
|
|
|
|
def _ensure_text_encoder(filename):
|
|
return _ensure_single_file("text_encoders", filename, TEXT_ENCODER_REPO, TEXT_ENCODER_REPO_FILE)
|
|
|
|
|
|
def _ensure_vae(filename):
|
|
return _ensure_single_file("vae", filename, VAE_REPO, VAE_REPO_FILE)
|
|
|
|
|
|
def _ensure_transformer(filename, single_file_repo, sharded_repo):
|
|
existing = _existing_path("diffusion_models", filename)
|
|
if existing is not None and not os.path.islink(existing):
|
|
return existing
|
|
target_path = _download_target_path("diffusion_models", filename)
|
|
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
|
|
if single_file_repo:
|
|
# pre-merged single file - a plain download, same as any other model file.
|
|
return _materialize_existing_or_downloaded(existing, target_path, single_file_repo, filename)
|
|
|
|
# no pre-merged repo configured - fetch the original sharded release and merge once.
|
|
from huggingface_hub import snapshot_download
|
|
|
|
snapshot_dir = snapshot_download(repo_id=sharded_repo, allow_patterns=[f"{TRANSFORMER_SUBFOLDER}/*"])
|
|
shard_dir = os.path.join(snapshot_dir, TRANSFORMER_SUBFOLDER)
|
|
index_path = os.path.join(shard_dir, "diffusion_pytorch_model.safetensors.index.json")
|
|
with open(index_path) as f:
|
|
weight_map = json.load(f)["weight_map"]
|
|
shard_files = sorted(set(weight_map.values()))
|
|
|
|
# merges all shards into one file, so this needs ~the full transformer's size in RAM at
|
|
# once (~38GB) - only ever runs the first time this file is missing, never during
|
|
# regular inference.
|
|
merged = {}
|
|
for shard_file in shard_files:
|
|
merged.update(load_safetensors(os.path.join(shard_dir, shard_file)))
|
|
|
|
save_safetensors(merged, target_path)
|
|
del merged
|
|
gc.collect()
|
|
return target_path
|
|
|
|
|
|
def clear_pipeline_cache():
|
|
with _PIPELINE_LOCK:
|
|
pipe = _PIPELINE_CACHE.get("pipe")
|
|
_PIPELINE_CACHE["pipe"] = None
|
|
_PIPELINE_CACHE["key"] = None
|
|
if pipe is not None:
|
|
try:
|
|
pipe.to("cpu")
|
|
except Exception:
|
|
pass
|
|
del pipe
|
|
gc.collect()
|
|
if torch.cuda.is_available():
|
|
torch.cuda.empty_cache()
|
|
|
|
|
|
def _load_state_dict(path, dtype):
|
|
sd = load_safetensors(path)
|
|
return {k: v.to(dtype=dtype) for k, v in sd.items()}
|
|
|
|
|
|
class _ComfyExternalVAEEncodeOutput:
|
|
def __init__(self, latents):
|
|
self.latents = latents
|
|
|
|
|
|
class _ComfyExternalVAEAdapter:
|
|
def __init__(self, comfy_vae, vae_config):
|
|
self._comfy_vae = comfy_vae
|
|
self.config = SimpleNamespace(**vae_config)
|
|
self.temperal_downsample = vae_config.get("temperal_downsample", [])
|
|
self.dtype = getattr(comfy_vae, "vae_dtype", PIPELINE_DTYPE)
|
|
|
|
def encode(self, image):
|
|
import comfy.model_management
|
|
|
|
with comfy.model_management.cuda_device_context(self._comfy_vae.device):
|
|
memory_used = self._comfy_vae.memory_used_encode(image.shape, self._comfy_vae.vae_dtype)
|
|
comfy.model_management.load_models_gpu(
|
|
[self._comfy_vae.patcher],
|
|
memory_required=memory_used,
|
|
force_full_load=self._comfy_vae.disable_offload,
|
|
)
|
|
latents = self._comfy_vae.first_stage_model.encode(
|
|
image.to(device=self._comfy_vae.device, dtype=self._comfy_vae.vae_dtype)
|
|
)
|
|
return _ComfyExternalVAEEncodeOutput(latents.to(dtype=self.dtype))
|
|
|
|
def decode(self, latents, return_dict=False):
|
|
import comfy.model_management
|
|
|
|
with comfy.model_management.cuda_device_context(self._comfy_vae.device):
|
|
memory_used = self._comfy_vae.memory_used_decode(latents.shape, self._comfy_vae.vae_dtype)
|
|
comfy.model_management.load_models_gpu(
|
|
[self._comfy_vae.patcher],
|
|
memory_required=memory_used,
|
|
force_full_load=self._comfy_vae.disable_offload,
|
|
)
|
|
image = self._comfy_vae.first_stage_model.decode(
|
|
latents.to(device=self._comfy_vae.device, dtype=self._comfy_vae.vae_dtype)
|
|
)
|
|
if return_dict:
|
|
return {"sample": image}
|
|
return (image,)
|
|
|
|
|
|
def _resolve_asset_paths(architecture):
|
|
arch = ARCHITECTURES[architecture]
|
|
transformer_path = _ensure_transformer(
|
|
arch["transformer_file"], arch["transformer_single_file_repo"], arch["transformer_sharded_repo"]
|
|
)
|
|
text_encoder_path = _ensure_text_encoder(TEXT_ENCODER_FILE)
|
|
vae_path = _ensure_vae(VAE_FILE)
|
|
style_lora_path = _ensure_lora(arch["style_lora"], arch["style_lora"])
|
|
speed_lora_path = _ensure_lora(arch["speed_lora"], arch["speed_lora"])
|
|
resolved = {
|
|
"architecture": architecture,
|
|
"transformer_path": transformer_path,
|
|
"text_encoder_path": text_encoder_path,
|
|
"vae_path": vae_path,
|
|
"style_lora_path": style_lora_path,
|
|
"speed_lora_path": speed_lora_path,
|
|
}
|
|
for label, path in (
|
|
("transformer", transformer_path),
|
|
("text_encoder", text_encoder_path),
|
|
("vae", vae_path),
|
|
("style_lora", style_lora_path),
|
|
("speed_lora", speed_lora_path),
|
|
):
|
|
if not path or not os.path.isfile(path):
|
|
raise FileNotFoundError(f"{label}: could not find or download {path}")
|
|
return resolved
|
|
|
|
|
|
def _configure_runtime_residency(pipe, resolved_device, offload_mode):
|
|
pipe.resolved_device = resolved_device
|
|
pipe.offload_mode = offload_mode
|
|
|
|
if resolved_device == "cpu" or offload_mode == "keep_loaded":
|
|
pipe.to(resolved_device)
|
|
return
|
|
|
|
if offload_mode == "model_cpu_offload":
|
|
try:
|
|
pipe.enable_model_cpu_offload(device=resolved_device)
|
|
return
|
|
except Exception as exc:
|
|
raise RuntimeError(
|
|
"Failed to enable model CPU offload. Install/configure diffusers offload dependencies "
|
|
"or switch offload_mode back to keep_loaded."
|
|
) from exc
|
|
|
|
if offload_mode == "sequential_cpu_offload":
|
|
try:
|
|
pipe.enable_sequential_cpu_offload(device=resolved_device)
|
|
return
|
|
except Exception as exc:
|
|
raise RuntimeError(
|
|
"Failed to enable sequential CPU offload. Install/configure diffusers offload dependencies "
|
|
"or switch offload_mode back to keep_loaded."
|
|
) from exc
|
|
|
|
raise ValueError(f"Unknown offload_mode: {offload_mode}")
|
|
|
|
|
|
def _build_pipeline(asset_paths, device, offload_mode, external_vae=None):
|
|
resolved_device = resolve_device(device)
|
|
|
|
vae_dir = os.path.join(ASSET_MODEL_DIR, "vae")
|
|
transformer_dir = os.path.join(ASSET_MODEL_DIR, "transformer")
|
|
text_encoder_dir = os.path.join(ASSET_MODEL_DIR, "text_encoder")
|
|
|
|
with torch.device("meta"):
|
|
vae_config = AutoencoderKLQwenImage.load_config(vae_dir)
|
|
vae_module = None if external_vae is not None else AutoencoderKLQwenImage.from_config(vae_config)
|
|
transformer_config = QwenImageTransformer2DModel.load_config(transformer_dir)
|
|
transformer_module = QwenImageTransformer2DModel.from_config(transformer_config)
|
|
text_encoder_config = AutoConfig.from_pretrained(text_encoder_dir)
|
|
|
|
# transformer weights are already in diffusers' own QwenImageTransformer2DModel key
|
|
# layout (this is literally the same file app.py's from_pretrained() reads), so this
|
|
# is a plain single load - no comfy involved, no second copy of anything.
|
|
transformer_sd = _load_state_dict(asset_paths["transformer_path"], PIPELINE_DTYPE)
|
|
transformer_module.load_state_dict(transformer_sd, strict=True, assign=True)
|
|
del transformer_sd
|
|
|
|
# transformer_module.pos_embed (QwenEmbedRope/QwenEmbedLayer3DRope) precomputes its
|
|
# rotary frequency tables as plain python attributes in __init__ - deliberately not
|
|
# registered buffers (registering them as buffers loses the complex dtype's imaginary
|
|
# part) - so building under torch.device("meta") leaves them as empty meta tensors
|
|
# forever; load_state_dict can't fix this since they were never part of any checkpoint.
|
|
# Rebuilding just this small (config-only, no big weights) submodule for real replaces
|
|
# them with correctly computed data.
|
|
pos_embed = transformer_module.pos_embed
|
|
transformer_module.pos_embed = type(pos_embed)(
|
|
theta=pos_embed.theta, axes_dim=pos_embed.axes_dim, scale_rope=pos_embed.scale_rope
|
|
)
|
|
|
|
# the vae file on disk is in ComfyUI's own WanVAE key layout (top-level conv1/conv2,
|
|
# encoder.downsamples.N...), not diffusers' AutoencoderKLQwenImage layout - reuse the
|
|
# same structural remapping the old comfy-socket bridge used, just fed a plain state
|
|
# dict here instead of a live comfy VAE object.
|
|
if external_vae is None:
|
|
vae_raw_sd = _load_state_dict(asset_paths["vae_path"], PIPELINE_DTYPE)
|
|
vae_sd = vae_convert.convert_and_validate(
|
|
vae_raw_sd,
|
|
vae_module,
|
|
dim_mult=vae_config.get("dim_mult", [1, 2, 4, 4]),
|
|
num_res_blocks=vae_config.get("num_res_blocks", 2),
|
|
attn_scales=vae_config.get("attn_scales", []),
|
|
)
|
|
vae_module.load_state_dict(vae_sd, strict=True, assign=True)
|
|
del vae_raw_sd, vae_sd
|
|
else:
|
|
vae_module = _ComfyExternalVAEAdapter(external_vae, vae_config)
|
|
|
|
# unlike a comfy CLIP object (conditioning-only, never carries lm_head), this flat file
|
|
# is the same checkpoint app.py loads directly and includes lm_head - so the
|
|
# prompt-enhancer (infer_with_content_prompt/infer_with_style_prompt) works here.
|
|
text_encoder_sd = _load_state_dict(asset_paths["text_encoder_path"], PIPELINE_DTYPE)
|
|
text_encoder_module, te_loading_info = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
|
None,
|
|
config=text_encoder_config,
|
|
state_dict=text_encoder_sd,
|
|
dtype=PIPELINE_DTYPE,
|
|
low_cpu_mem_usage=True,
|
|
output_loading_info=True,
|
|
)
|
|
del text_encoder_sd
|
|
bad_loading = {
|
|
k: v
|
|
for k, v in te_loading_info.items()
|
|
if k in ("missing_keys", "unexpected_keys", "mismatched_keys") and v
|
|
}
|
|
if bad_loading:
|
|
raise RuntimeError(f"text encoder: from_pretrained reported loading problems: {bad_loading}")
|
|
has_lm_head = "lm_head.weight" not in (te_loading_info.get("missing_keys") or [])
|
|
|
|
tokenizer = Qwen2Tokenizer.from_pretrained(os.path.join(ASSET_MODEL_DIR, "tokenizer"))
|
|
processor = Qwen2VLProcessor.from_pretrained(os.path.join(ASSET_MODEL_DIR, "processor"))
|
|
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(os.path.join(ASSET_MODEL_DIR, "scheduler"))
|
|
|
|
pipe = QwenImageEditPlusPipeline(
|
|
scheduler=scheduler,
|
|
vae=vae_module,
|
|
text_encoder=text_encoder_module,
|
|
tokenizer=tokenizer,
|
|
processor=processor,
|
|
transformer=transformer_module,
|
|
)
|
|
pipe.set_progress_bar_config(disable=True)
|
|
pipe.has_lm_head = has_lm_head
|
|
_configure_runtime_residency(pipe, resolved_device, offload_mode)
|
|
|
|
pipe.load_lora_weights(asset_paths["style_lora_path"], adapter_name="style")
|
|
pipe.load_lora_weights(asset_paths["speed_lora_path"], adapter_name="dmd")
|
|
pipe.set_adapters(["style", "dmd"], adapter_weights=[1.0, 1.0])
|
|
pipe.fuse_lora(adapter_names=["style", "dmd"], lora_scale=1.0)
|
|
pipe.unload_lora_weights()
|
|
return pipe
|
|
|
|
|
|
class QwenTeleStyleLoader:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"architecture": (list(ARCHITECTURES.keys()), {"default": "2509"}),
|
|
"device": (["cuda", "cpu", "auto"], {"default": "cuda" if torch.cuda.is_available() else "cpu"}),
|
|
"offload_mode": (list(OFFLOAD_MODES), {"default": "keep_loaded"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("TELESTYLE_MODELS",)
|
|
RETURN_NAMES = ("models",)
|
|
FUNCTION = "load"
|
|
CATEGORY = "image/TeleStyleV2"
|
|
|
|
def load(self, architecture, device, offload_mode):
|
|
resolved_device = resolve_device(device)
|
|
asset_paths = _resolve_asset_paths(architecture)
|
|
|
|
cache_key = (
|
|
asset_paths["transformer_path"],
|
|
asset_paths["text_encoder_path"],
|
|
asset_paths["vae_path"],
|
|
asset_paths["style_lora_path"],
|
|
asset_paths["speed_lora_path"],
|
|
resolved_device,
|
|
offload_mode,
|
|
)
|
|
with _PIPELINE_LOCK:
|
|
if _PIPELINE_CACHE["key"] == cache_key and _PIPELINE_CACHE["pipe"] is not None:
|
|
return (
|
|
{
|
|
"mode": "standalone",
|
|
"pipe": _PIPELINE_CACHE["pipe"],
|
|
"resolved_device": resolved_device,
|
|
"offload_mode": offload_mode,
|
|
"architecture": architecture,
|
|
},
|
|
)
|
|
|
|
clear_pipeline_cache()
|
|
pipe = _build_pipeline(asset_paths, device, offload_mode)
|
|
with _PIPELINE_LOCK:
|
|
_PIPELINE_CACHE["key"] = cache_key
|
|
_PIPELINE_CACHE["pipe"] = pipe
|
|
return (
|
|
{
|
|
"mode": "standalone",
|
|
"pipe": pipe,
|
|
"resolved_device": resolved_device,
|
|
"offload_mode": offload_mode,
|
|
"architecture": architecture,
|
|
"shared_components": (),
|
|
},
|
|
)
|
|
|
|
|
|
class QwenTeleStyleSharedVAELoader:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"vae": ("VAE",),
|
|
"architecture": (list(ARCHITECTURES.keys()), {"default": "2509"}),
|
|
"device": (["cuda", "cpu", "auto"], {"default": "cuda" if torch.cuda.is_available() else "cpu"}),
|
|
"offload_mode": (list(OFFLOAD_MODES), {"default": "keep_loaded"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("TELESTYLE_MODELS",)
|
|
RETURN_NAMES = ("models",)
|
|
FUNCTION = "load"
|
|
CATEGORY = "image/TeleStyleV2/experimental"
|
|
|
|
def load(self, vae, architecture, device, offload_mode):
|
|
resolved_device = resolve_device(device)
|
|
asset_paths = _resolve_asset_paths(architecture)
|
|
vae_cache_token = id(getattr(vae, "patcher", vae))
|
|
|
|
cache_key = (
|
|
asset_paths["transformer_path"],
|
|
asset_paths["text_encoder_path"],
|
|
"external_vae",
|
|
vae_cache_token,
|
|
asset_paths["style_lora_path"],
|
|
asset_paths["speed_lora_path"],
|
|
resolved_device,
|
|
offload_mode,
|
|
)
|
|
with _PIPELINE_LOCK:
|
|
if _PIPELINE_CACHE["key"] == cache_key and _PIPELINE_CACHE["pipe"] is not None:
|
|
return (
|
|
{
|
|
"mode": "standalone_external_vae",
|
|
"pipe": _PIPELINE_CACHE["pipe"],
|
|
"resolved_device": resolved_device,
|
|
"offload_mode": offload_mode,
|
|
"architecture": architecture,
|
|
"shared_components": ("vae",),
|
|
},
|
|
)
|
|
|
|
clear_pipeline_cache()
|
|
pipe = _build_pipeline(asset_paths, device, offload_mode, external_vae=vae)
|
|
with _PIPELINE_LOCK:
|
|
_PIPELINE_CACHE["key"] = cache_key
|
|
_PIPELINE_CACHE["pipe"] = pipe
|
|
return (
|
|
{
|
|
"mode": "standalone_external_vae",
|
|
"pipe": pipe,
|
|
"resolved_device": resolved_device,
|
|
"offload_mode": offload_mode,
|
|
"architecture": architecture,
|
|
"shared_components": ("vae",),
|
|
},
|
|
)
|
|
|
|
|
|
class QwenTeleStyleUnload:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {}}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("status",)
|
|
FUNCTION = "unload"
|
|
CATEGORY = "image/TeleStyleV2"
|
|
|
|
def unload(self):
|
|
clear_pipeline_cache()
|
|
return ("Qwen TeleStyle pipeline cache cleared.",)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Qwen TeleStyle Loader": QwenTeleStyleLoader,
|
|
"Qwen TeleStyle Loader (Shared VAE Experimental)": QwenTeleStyleSharedVAELoader,
|
|
"Qwen TeleStyle Unload": QwenTeleStyleUnload,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"Qwen TeleStyle Loader": "Qwen TeleStyle Loader",
|
|
"Qwen TeleStyle Loader (Shared VAE Experimental)": "Qwen TeleStyle Loader (Shared VAE Experimental)",
|
|
"Qwen TeleStyle Unload": "Qwen TeleStyle Unload",
|
|
}
|