233 lines
7.4 KiB
Python
233 lines
7.4 KiB
Python
"""
|
|
Qwen model/resource discovery helpers.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
try:
|
|
import folder_paths
|
|
except Exception:
|
|
folder_paths = None
|
|
|
|
|
|
TEXT_MODEL_PLACEHOLDER = "(no qwen text-to-image models found)"
|
|
EDIT_MODEL_PLACEHOLDER = "(no qwen image edit models found)"
|
|
AUTO_TURBO_LORA_CHOICE = "(auto)"
|
|
NO_TEXT_TURBO_LORA_PLACEHOLDER = "(no qwen text turbo loras found)"
|
|
NO_EDIT_TURBO_LORA_PLACEHOLDER = "(no qwen edit turbo loras found)"
|
|
REFERENCE_LATENTS_METHOD_CHOICES = ["offset", "index", "uxo/uno", "index_timestep_zero"]
|
|
|
|
_QWEN_TEXT_ENCODER_PATTERNS = (
|
|
"qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
|
"qwen_2.5_vl_7b",
|
|
"qwen25_7b",
|
|
)
|
|
_QWEN_VAE_PATTERNS = (
|
|
"qwen_image_vae.safetensors",
|
|
"qwen_image_vae",
|
|
)
|
|
|
|
|
|
def _get_filename_list(category: str) -> list[str]:
|
|
if folder_paths is None:
|
|
return []
|
|
try:
|
|
return list(folder_paths.get_filename_list(category))
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
def _sorted_unique(items: list[str]) -> list[str]:
|
|
return sorted(dict.fromkeys(item for item in items if item))
|
|
|
|
|
|
def _match_by_patterns(names: list[str], patterns: tuple[str, ...]) -> str | None:
|
|
lowered = {name.lower(): name for name in names}
|
|
for pattern in patterns:
|
|
pattern_lower = pattern.lower()
|
|
for candidate_lower, candidate in lowered.items():
|
|
if candidate_lower == pattern_lower or pattern_lower in candidate_lower:
|
|
return candidate
|
|
return None
|
|
|
|
|
|
def _lower(value: str | None) -> str:
|
|
return (value or "").lower()
|
|
|
|
|
|
def is_qwen_text_to_image_model(name: str | None) -> bool:
|
|
lowered = _lower(name)
|
|
return (
|
|
"qwen" in lowered
|
|
and "image" in lowered
|
|
and "edit" not in lowered
|
|
and "layered" not in lowered
|
|
)
|
|
|
|
|
|
def is_qwen_image_edit_model(name: str | None) -> bool:
|
|
lowered = _lower(name)
|
|
return "qwen" in lowered and "image" in lowered and "edit" in lowered and "layered" not in lowered
|
|
|
|
|
|
def get_qwen_text_model_choices() -> list[str]:
|
|
names = [name for name in _get_filename_list("diffusion_models") if is_qwen_text_to_image_model(name)]
|
|
return _sorted_unique(names) or [TEXT_MODEL_PLACEHOLDER]
|
|
|
|
|
|
def get_qwen_edit_model_choices() -> list[str]:
|
|
names = [name for name in _get_filename_list("diffusion_models") if is_qwen_image_edit_model(name)]
|
|
return _sorted_unique(names) or [EDIT_MODEL_PLACEHOLDER]
|
|
|
|
|
|
def validate_qwen_text_model_name(model_name: str) -> str:
|
|
if not is_qwen_text_to_image_model(model_name):
|
|
raise RuntimeError(
|
|
f"[LLS] Model '{model_name}' is not compatible with LLSQwenTextToImage."
|
|
)
|
|
return model_name
|
|
|
|
|
|
def validate_qwen_edit_model_name(model_name: str) -> str:
|
|
if not is_qwen_image_edit_model(model_name):
|
|
raise RuntimeError(
|
|
f"[LLS] Model '{model_name}' is not compatible with LLSQwenImageEdit."
|
|
)
|
|
return model_name
|
|
|
|
|
|
def is_qwen_text_turbo_lora(name: str | None) -> bool:
|
|
lowered = _lower(name)
|
|
return "qwen-image" in lowered and "lightning" in lowered and "edit" not in lowered
|
|
|
|
|
|
def is_qwen_edit_turbo_lora(name: str | None) -> bool:
|
|
lowered = _lower(name)
|
|
return "qwen-image-edit" in lowered and "lightning" in lowered
|
|
|
|
|
|
def get_qwen_text_turbo_lora_choices() -> list[str]:
|
|
names = _sorted_unique([name for name in _get_filename_list("loras") if is_qwen_text_turbo_lora(name)])
|
|
if not names:
|
|
return [AUTO_TURBO_LORA_CHOICE, NO_TEXT_TURBO_LORA_PLACEHOLDER]
|
|
return [AUTO_TURBO_LORA_CHOICE] + names
|
|
|
|
|
|
def get_qwen_edit_turbo_lora_choices() -> list[str]:
|
|
names = _sorted_unique([name for name in _get_filename_list("loras") if is_qwen_edit_turbo_lora(name)])
|
|
if not names:
|
|
return [AUTO_TURBO_LORA_CHOICE, NO_EDIT_TURBO_LORA_PLACEHOLDER]
|
|
return [AUTO_TURBO_LORA_CHOICE] + names
|
|
|
|
|
|
def _resolve_candidate_turbo_loras(model_name: str, available: list[str]) -> list[str]:
|
|
lowered = _lower(model_name)
|
|
if is_qwen_text_to_image_model(model_name):
|
|
if "2512" in lowered:
|
|
return _sorted_unique([name for name in available if "2512" in _lower(name)])
|
|
return _sorted_unique(
|
|
[
|
|
name for name in available
|
|
if "2512" not in _lower(name)
|
|
and "2509" not in _lower(name)
|
|
and "2511" not in _lower(name)
|
|
]
|
|
)
|
|
|
|
if is_qwen_image_edit_model(model_name):
|
|
if "2511" in lowered:
|
|
return _sorted_unique([name for name in available if "2511" in _lower(name)])
|
|
if "2509" in lowered:
|
|
return _sorted_unique([name for name in available if "2509" in _lower(name)])
|
|
return []
|
|
|
|
|
|
def _resolve_turbo_lora(
|
|
*,
|
|
model_name: str,
|
|
requested_name: str,
|
|
all_loras: list[str],
|
|
available: list[str],
|
|
model_label: str,
|
|
) -> str:
|
|
candidates = _resolve_candidate_turbo_loras(model_name, available)
|
|
|
|
if requested_name == AUTO_TURBO_LORA_CHOICE:
|
|
if candidates:
|
|
return candidates[0]
|
|
raise RuntimeError(f"[LLS] No compatible turbo/lightning LoRA exists for '{model_name}'.")
|
|
|
|
if requested_name not in all_loras:
|
|
raise RuntimeError(
|
|
f"[LLS] Turbo LoRA '{requested_name}' was not found in ComfyUI/models/loras/."
|
|
)
|
|
|
|
if requested_name not in available or requested_name not in candidates:
|
|
raise RuntimeError(
|
|
f"[LLS] Turbo LoRA '{requested_name}' is not compatible with {model_label} model '{model_name}'."
|
|
)
|
|
|
|
return requested_name
|
|
|
|
|
|
def resolve_qwen_text_turbo_lora(model_name: str, requested_name: str) -> str:
|
|
all_loras = _sorted_unique(_get_filename_list("loras"))
|
|
available = [name for name in all_loras if is_qwen_text_turbo_lora(name)]
|
|
return _resolve_turbo_lora(
|
|
model_name=model_name,
|
|
requested_name=requested_name,
|
|
all_loras=all_loras,
|
|
available=available,
|
|
model_label="text-to-image",
|
|
)
|
|
|
|
|
|
def resolve_qwen_edit_turbo_lora(model_name: str, requested_name: str) -> str:
|
|
all_loras = _sorted_unique(_get_filename_list("loras"))
|
|
available = [name for name in all_loras if is_qwen_edit_turbo_lora(name)]
|
|
return _resolve_turbo_lora(
|
|
model_name=model_name,
|
|
requested_name=requested_name,
|
|
all_loras=all_loras,
|
|
available=available,
|
|
model_label="image-edit",
|
|
)
|
|
|
|
|
|
def get_qwen_turbo_profile(model_name: str) -> dict[str, float] | None:
|
|
lowered = _lower(model_name)
|
|
if is_qwen_image_edit_model(model_name):
|
|
if "2509" in lowered or "2511" in lowered:
|
|
return {"steps": 4, "cfg": 1.0}
|
|
return None
|
|
|
|
if is_qwen_text_to_image_model(model_name):
|
|
if "2512" in lowered:
|
|
return {"steps": 4, "cfg": 1.0}
|
|
return {"steps": 8, "cfg": 1.0}
|
|
|
|
return None
|
|
|
|
|
|
def resolve_qwen_text_encoder_name() -> str | None:
|
|
names = _get_filename_list("text_encoders")
|
|
matched = _match_by_patterns(names, _QWEN_TEXT_ENCODER_PATTERNS)
|
|
if matched is not None:
|
|
return matched
|
|
for name in names:
|
|
lowered = _lower(name)
|
|
if "qwen" in lowered and ("vl" in lowered or "qwen25_7b" in lowered):
|
|
return name
|
|
return None
|
|
|
|
|
|
def resolve_qwen_vae_name() -> str | None:
|
|
names = _get_filename_list("vae")
|
|
matched = _match_by_patterns(names, _QWEN_VAE_PATTERNS)
|
|
if matched is not None:
|
|
return matched
|
|
for name in names:
|
|
lowered = _lower(name)
|
|
if "qwen" in lowered and "vae" in lowered and "layered" not in lowered:
|
|
return name
|
|
return None
|