Files
AEmotionStudio-ComfyUI-Shad…/core/model_compat.py
T
AEmotionStudio ab44bbcb2b fix: Resolve shader registration, unused variable, and parameter mismatch
- register_shader_generator now registers to both legacy dict and new registry
- Removed unused inner_class_lower variable in model_compat.py
- Changed generator_args key from 'shader_params' to 'params' to match BaseNoiseGenerator signature
- Added ShaderParams import and wrap params before passing to generators
2026-01-26 21:29:12 -08:00

367 lines
12 KiB
Python

"""
Model compatibility utilities for detecting model types and channel counts.
"""
import torch
from typing import Optional, Any, Tuple
from .constants import (
DEFAULT_CHANNELS,
MODEL_CHANNEL_COUNTS,
VIDEO_MODEL_CHANNELS,
)
def get_model_channel_count(model: Any, debugger: Optional[object] = None) -> int:
"""
Determine the number of channels in the latent space for a given model.
This is important for matching the correct noise shape to the model's expectations.
Args:
model: The model to analyze
debugger: Optional debugger for logging
Returns:
Number of channels (4 for SD1.5/2.x, 16 for WAN2.1/Flux/SD3, etc.)
"""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
# Get model identifiers
model_name = _get_model_name(model)
model_type = _get_model_type(model)
model_class = model.__class__.__name__
if debug_enabled:
print(f"🔎 Model detection - name: '{model_name}', type: '{model_type}', class: '{model_class}'")
# Check inner model if wrapped
if hasattr(model, 'model'):
inner_channels = _check_inner_model(model.model, debugger)
if inner_channels is not None:
return inner_channels
# Check latent format
if hasattr(model, 'latent_format') and hasattr(model.latent_format, 'latent_channels'):
channels = model.latent_format.latent_channels
if debug_enabled:
print(f"✅ Found latent_format.latent_channels: {channels}")
return channels
# Check diffusion model structure
channels = _check_diffusion_model(model, debugger)
if channels is not None:
return channels
# Check by model name patterns
channels = _check_model_name_patterns(model_name, model_class, debugger)
if channels is not None:
return channels
# Check model path if available
model_path = getattr(model, 'model_path', "").lower()
if model_path:
channels = _check_model_path(model_path, debugger)
if channels is not None:
return channels
# Default for most models (SD1.5, SD2.x, SDXL, etc.)
if debug_enabled:
print(f"🔄 Using default channel count: {DEFAULT_CHANNELS}")
return DEFAULT_CHANNELS
def _get_model_name(model: Any) -> str:
"""Extract model name from model object."""
model_name = getattr(model, 'model_name', "")
if isinstance(model_name, str):
return model_name.lower()
return ""
def _get_model_type(model: Any) -> str:
"""Extract model type from model object."""
model_type_attr = getattr(model, 'model_type', "")
if model_type_attr is not None:
return str(model_type_attr).upper()
return ""
def _check_inner_model(inner_model: Any, debugger: Optional[object] = None) -> Optional[int]:
"""Check inner model for channel count."""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
inner_name = _get_model_name(inner_model)
inner_type = _get_model_type(inner_model)
inner_class = inner_model.__class__.__name__
if debug_enabled:
print(f"🔎 Inner model - name: '{inner_name}', type: '{inner_type}', class: '{inner_class}'")
# Check for specific model classes
class_channel_map = {
"HiDream": 16,
"ACEStep": 8,
"ACE": 8,
"WAN21": 16,
"Mochi": 12,
"LTXV": 128,
"CosmosVideo": 16,
"Cosmos1CV8x8x8": 16,
"HunyuanVideo": 16,
}
if inner_class in class_channel_map:
channels = class_channel_map[inner_class]
if debug_enabled:
print(f"✅ Detected {inner_class} in inner model - using {channels} channels")
return channels
# Check for FLOW type
if inner_type == "FLOW":
if debug_enabled:
print(f"✅ Detected HiDream/Flow in inner model - using 16 channels")
return 16
# Check for WAN/Warp in name
if any(ind in inner_name for ind in ["wan", "warp"]):
if debug_enabled:
print(f"✅ Detected WAN/Warp in inner model - using 16 channels")
return 16
# Check for Stable Cascade Prior
if "stable_cascade_prior" in inner_name or "stablecascade_prior" in inner_name:
if debug_enabled:
print(f"✅ Detected Stable Cascade Prior in inner model - using 16 channels")
return 16
return None
def _check_diffusion_model(model: Any, debugger: Optional[object] = None) -> Optional[int]:
"""Check diffusion model structure for channel count."""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
debug_level = getattr(debugger, 'debug_level', 0) if debug_enabled else 0
if not hasattr(model, 'diffusion_model'):
return None
diffusion_model = model.diffusion_model
# Check input blocks
if hasattr(diffusion_model, 'input_blocks') and len(diffusion_model.input_blocks) > 0:
if hasattr(diffusion_model.input_blocks[0], 'in_channels'):
channels = diffusion_model.input_blocks[0].in_channels
# In_channels is often double the latent space channels
if channels in [8, 12, 16]:
actual_channels = channels // 2
if debug_enabled:
print(f"✅ Detected {actual_channels} channels from model's input_blocks[0].in_channels={channels}")
return actual_channels
# Check patch embedding
if hasattr(diffusion_model, 'patch_embedding'):
patch_shape = diffusion_model.patch_embedding.weight.shape
if len(patch_shape) >= 2:
channels = patch_shape[1]
if debug_enabled:
print(f"✅ Found patch_embedding with input channels: {channels}")
return channels
# Debug logging for diffusion model structure
if debug_enabled and debug_level >= 2:
if hasattr(diffusion_model, 'in_channels'):
print(f"🔎 Model's diffusion_model.in_channels: {diffusion_model.in_channels}")
return None
def _check_model_name_patterns(
model_name: str,
model_class: str,
debugger: Optional[object] = None
) -> Optional[int]:
"""Check model name patterns for channel count."""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
# Model name patterns
name_patterns = {
("wan", "warp", "pixel", "anime"): 16,
("sd3",): 16,
("flux",): 16,
("mochi",): 12,
("ltxv",): 128,
("cosmos",): 16,
("stable_cascade_prior", "stablecascade_prior"): 16,
}
for patterns, channels in name_patterns.items():
if any(p in model_name for p in patterns):
if debug_enabled:
print(f"✅ Detected model by name pattern - using {channels} channels")
return channels
# Special case for non-prior Stable Cascade
if "stable_cascade" in model_name:
if debug_enabled:
print(f"✅ Detected Stable Cascade (non-prior) model by name - using 4 channels")
return 4
# Check class names
model_class_lower = model_class.lower()
if model_class_lower == 'flux':
if debug_enabled:
print(f"✅ Detected Flux model by class - using 16 channels")
return 16
elif model_class_lower == 'hidream':
if debug_enabled:
print(f"✅ Detected HiDream model by class - using 16 channels")
return 16
return None
def _check_model_path(model_path: str, debugger: Optional[object] = None) -> Optional[int]:
"""Check model path for channel count hints."""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
wan_indicators = ["wan", "warp", "pixel", "anime"]
if any(ind in model_path for ind in wan_indicators):
if debug_enabled:
print(f"✅ Detected WAN/Warp/Pixel/Anime model from model_path - using 16 channels")
return 16
return None
def detect_latent_format(
latent_shape: Tuple[int, ...],
target_channels: int,
debugger: Optional[object] = None
) -> Tuple[str, int, int, int, int, int, int]:
"""
Detect the format of a latent tensor and extract dimensions.
Args:
latent_shape: Shape of the latent tensor
target_channels: Expected channel count from model
debugger: Optional debugger for logging
Returns:
Tuple of (format_name, batch_size, frames, channels, height, width,
channel_dim_idx, frame_dim_idx)
"""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
is_video = len(latent_shape) == 5
batch_size = latent_shape[0]
height = latent_shape[-2]
width = latent_shape[-1]
if is_video:
return _detect_video_format(latent_shape, target_channels, debugger)
else:
# 4D: [B, C, H, W]
channels = latent_shape[1]
frames = 1
channel_dim_idx = 1
frame_dim_idx = -1
detected_format = "[B, C, H, W]"
# Validate against model channels
if channels != target_channels:
if debug_enabled:
print(f"⚠️ Image shape {latent_shape} channel count ({channels}) differs "
f"from model's target channels ({target_channels}). "
f"Using model's target channels for noise generation.")
channels = target_channels
return (detected_format, batch_size, frames, channels, height, width,
channel_dim_idx, frame_dim_idx)
def _detect_video_format(
latent_shape: Tuple[int, ...],
target_channels: int,
debugger: Optional[object] = None
) -> Tuple[str, int, int, int, int, int, int, int]:
"""Detect video tensor format."""
debug_enabled = debugger is not None and getattr(debugger, 'enabled', False)
batch_size = latent_shape[0]
latent_dim1 = latent_shape[1]
latent_dim2 = latent_shape[2]
height = latent_shape[-2]
width = latent_shape[-1]
# Logic to determine Frame vs Channel dimension for 5D tensor
if latent_dim1 == target_channels and latent_dim2 != target_channels:
# Format likely [B, C, F, H, W]
channels = latent_dim1
frames = latent_dim2
channel_dim_idx = 1
frame_dim_idx = 2
detected_format = "[B, C, F, H, W]"
elif latent_dim2 == target_channels and latent_dim1 != target_channels:
# Format likely [B, F, C, H, W]
channels = latent_dim2
frames = latent_dim1
channel_dim_idx = 2
frame_dim_idx = 1
detected_format = "[B, F, C, H, W]"
elif latent_dim1 == target_channels and latent_dim2 == target_channels:
# Ambiguous - default to B,F,C,H,W
if debug_enabled:
print(f"⚠️ Ambiguous video shape {latent_shape} where both dim 1 & 2 match "
f"target channels {target_channels}. Assuming [B, F, C, H, W] format.")
channels = target_channels
frames = latent_dim1
channel_dim_idx = 2
frame_dim_idx = 1
detected_format = "[B, F, C, H, W] (Ambiguous)"
else:
# Neither matches - fallback to B,F,C,H,W
channels = target_channels
frames = latent_dim1
channel_dim_idx = 2
frame_dim_idx = 1
detected_format = "[B, F, C, H, W] (Fallback)"
return (detected_format, batch_size, frames, channels, height, width,
channel_dim_idx, frame_dim_idx)
def build_noise_shape(
detected_format: str,
batch_size: int,
frames: int,
target_channels: int,
height: int,
width: int,
is_video: bool
) -> Tuple[int, ...]:
"""
Build the target noise shape based on detected format.
Args:
detected_format: Detected tensor format string
batch_size: Batch size
frames: Number of frames (for video)
target_channels: Target channel count
height: Height dimension
width: Width dimension
is_video: Whether this is a video tensor
Returns:
Target noise shape tuple
"""
if is_video:
if detected_format.startswith("[B, C, F"):
# [B, C, F, H, W] format
return (batch_size, target_channels, frames, height, width)
else:
# Default to [B, F, C, H, W] format
return (batch_size, frames, target_channels, height, width)
else:
# [B, C, H, W] format
return (batch_size, target_channels, height, width)