193 lines
7.5 KiB
Python
193 lines
7.5 KiB
Python
import json
|
|
import logging
|
|
import torch
|
|
from typing import Dict
|
|
from comfy.model_detection import detect_unet_config
|
|
from .supported_models import models
|
|
|
|
|
|
def _has_gm_heads(sd: Dict[str, object], prefix: str) -> bool:
|
|
return f"{prefix}proj_out_means.weight" in sd and f"{prefix}proj_out_logweights.weight" in sd
|
|
|
|
|
|
def _infer_gm_cfg(sd: Dict[str, torch.Tensor], prefix: str, base_config: Dict):
|
|
"""
|
|
Infer patch_size, num_gaussians, out_channels from GM heads.
|
|
proj_out_logweights: (num_gaussians * (patch_size * patch_size), inner_dim)
|
|
proj_out_means: (num_gaussians * out_channels, inner_dim)
|
|
"""
|
|
base_image_model = base_config["image_model"]
|
|
if base_image_model in ('qwen_image', 'flux', 'flux2'):
|
|
patch_size = 2
|
|
else:
|
|
raise ValueError(f"Unknown base image model: {base_image_model}")
|
|
proj_out_logweights = sd[f"{prefix}proj_out_logweights.weight"]
|
|
proj_out_means = sd[f"{prefix}proj_out_means.weight"]
|
|
proj_out_logweights_out_channels = proj_out_logweights.shape[0]
|
|
num_gaussians = proj_out_logweights_out_channels // (patch_size * patch_size)
|
|
out_channels = proj_out_means.shape[0] // max(1, num_gaussians)
|
|
if base_image_model in ('flux', 'flux2'):
|
|
out_channels = out_channels // 4
|
|
return patch_size, num_gaussians, out_channels
|
|
|
|
|
|
def _infer_gm_logstd_cfg(sd: dict, prefix: str):
|
|
weight_shapes = []
|
|
base = f"{prefix}proj_out_logstds."
|
|
for k, v in sd.items():
|
|
if k.startswith(base) and k.endswith(".weight"):
|
|
# v is a Linear weight: (out_features, in_features)
|
|
weight_shapes.append(v.shape)
|
|
|
|
gm_num_logstd_layers = len(weight_shapes)
|
|
|
|
if gm_num_logstd_layers == 0:
|
|
constant_logstd = '-inf'
|
|
logstd_inner_dim = None
|
|
|
|
elif gm_num_logstd_layers == 1:
|
|
constant_logstd = logstd_inner_dim = None
|
|
|
|
else:
|
|
constant_logstd = None
|
|
non_final_outs = [weight_shape[0] for weight_shape in weight_shapes if weight_shape[0] != 1]
|
|
assert len(non_final_outs) > 0
|
|
logstd_inner_dim = non_final_outs[0]
|
|
|
|
return gm_num_logstd_layers, logstd_inner_dim, constant_logstd
|
|
|
|
|
|
def detect_policy_config(metadata=None):
|
|
if metadata is not None and 'policy_config' in metadata:
|
|
policy_config = json.loads(metadata["policy_config"])
|
|
if policy_config.get("mode") == "polynomial":
|
|
raise ValueError("DX polynomial policies are not supported by ComfyUI-piFlow.")
|
|
return policy_config
|
|
return {}
|
|
|
|
|
|
def detect_piflow_config(state_dict, key_prefix, metadata=None):
|
|
base_config = detect_unet_config(state_dict, key_prefix, metadata=metadata)
|
|
if base_config is None:
|
|
return None, None
|
|
|
|
base_image_model = base_config["image_model"]
|
|
|
|
if not _has_gm_heads(state_dict, key_prefix): # not a GMFlow model
|
|
if base_image_model == 'qwen_image':
|
|
# infer out_channels from proj_out.weight
|
|
patch_size = base_config.get("patch_size", 2)
|
|
proj_out_weight = state_dict[f"{key_prefix}proj_out.weight"]
|
|
out_channels = proj_out_weight.shape[0] // (patch_size * patch_size)
|
|
unet_config = base_config.copy()
|
|
unet_config["out_channels"] = out_channels
|
|
elif base_image_model in ('flux', 'flux2'):
|
|
# infer out_channels from proj_out.weight
|
|
patch_size = base_config.get("patch_size", 2)
|
|
proj_out_weight = state_dict[f"{key_prefix}final_layer.linear.weight"]
|
|
out_channels = proj_out_weight.shape[0] // (patch_size * patch_size)
|
|
unet_config = base_config.copy()
|
|
unet_config["out_channels"] = out_channels
|
|
else:
|
|
unet_config = base_config
|
|
|
|
else:
|
|
patch_size, num_gaussians, out_channels = _infer_gm_cfg(state_dict, key_prefix, base_config)
|
|
gm_num_logstd_layers, logstd_inner_dim, constant_logstd = _infer_gm_logstd_cfg(state_dict, key_prefix)
|
|
unet_config = base_config.copy()
|
|
unet_config["image_model"] = f"gm_{base_image_model}"
|
|
unet_config["patch_size"] = patch_size
|
|
unet_config["num_gaussians"] = num_gaussians
|
|
unet_config["out_channels"] = out_channels
|
|
unet_config["gm_num_logstd_layers"] = gm_num_logstd_layers
|
|
unet_config["logstd_inner_dim"] = logstd_inner_dim
|
|
unet_config["constant_logstd"] = constant_logstd
|
|
|
|
return unet_config, detect_policy_config(metadata)
|
|
|
|
|
|
def model_config_from_piflow_config(unet_config, policy_config, state_dict=None):
|
|
for model_config in models:
|
|
if model_config.matches(unet_config, state_dict):
|
|
model_config = model_config(unet_config)
|
|
if policy_config:
|
|
if getattr(model_config, "policy_config", None) is None:
|
|
model_config.policy_config = {}
|
|
model_config.policy_config.update(policy_config)
|
|
return model_config
|
|
|
|
logging.error("no match {}".format(unet_config))
|
|
return None
|
|
|
|
|
|
def model_config_from_piflow(state_dict, key_prefix, metadata=None):
|
|
unet_config, policy_config = detect_piflow_config(
|
|
state_dict, key_prefix, metadata=metadata)
|
|
if unet_config is None:
|
|
return None
|
|
model_config = model_config_from_piflow_config(
|
|
unet_config, policy_config, state_dict)
|
|
if model_config is None:
|
|
return None
|
|
|
|
scaled_fp8_key = "{}scaled_fp8".format(key_prefix)
|
|
if scaled_fp8_key in state_dict:
|
|
scaled_fp8_weight = state_dict.pop(scaled_fp8_key)
|
|
model_config.scaled_fp8 = scaled_fp8_weight.dtype
|
|
if model_config.scaled_fp8 == torch.float32:
|
|
model_config.scaled_fp8 = torch.float8_e4m3fn
|
|
if scaled_fp8_weight.nelement() == 2:
|
|
model_config.optimizations["fp8"] = False
|
|
else:
|
|
model_config.optimizations["fp8"] = True
|
|
|
|
return model_config
|
|
|
|
|
|
def detect_asymflow_config(state_dict, key_prefix, metadata=None):
|
|
base_config = detect_unet_config(state_dict, key_prefix, metadata=metadata)
|
|
if base_config is None:
|
|
return None
|
|
|
|
has_asym_buffers = (
|
|
f"{key_prefix}proj_buffer" in state_dict
|
|
and f"{key_prefix}scale_buffer" in state_dict
|
|
)
|
|
if not has_asym_buffers:
|
|
return None
|
|
|
|
base_image_model = base_config.get("image_model")
|
|
if base_image_model != "flux2":
|
|
logging.error("AsymFlow detected, but only Flux2 Klein is supported right now: %s", base_config)
|
|
return None
|
|
|
|
unet_config = base_config.copy()
|
|
unet_config["image_model"] = "asym_flux2"
|
|
|
|
proj_buffer = state_dict.get(f"{key_prefix}proj_buffer")
|
|
if proj_buffer is not None:
|
|
patch_dim = proj_buffer.shape[0]
|
|
unet_config["base_rank"] = proj_buffer.shape[1]
|
|
else:
|
|
patch_dim = state_dict[f"{key_prefix}img_in.weight"].shape[1]
|
|
unet_config["base_rank"] = min(128, patch_dim)
|
|
|
|
# First supported AsymFlow architecture is RGB/Oklab pixel diffusion.
|
|
unet_config["in_channels"] = 3
|
|
unet_config["out_channels"] = 3
|
|
unet_config["patch_size"] = int(round((patch_dim // 3) ** 0.5))
|
|
unet_config["sigma_min"] = float(metadata.get("sigma_min", 1e-4)) if metadata is not None else 1e-4
|
|
unet_config["num_timesteps"] = float(metadata.get("num_timesteps", 1.0)) if metadata is not None else 1.0
|
|
return unet_config
|
|
|
|
|
|
def model_config_from_asymflow(state_dict, key_prefix, metadata=None):
|
|
unet_config = detect_asymflow_config(state_dict, key_prefix, metadata=metadata)
|
|
if unet_config is None:
|
|
return None
|
|
model_config = model_config_from_piflow_config(unet_config, {}, state_dict)
|
|
if model_config is None:
|
|
return None
|
|
|
|
return model_config
|