Files
automatic/modules/sd_detect.py
T
CalamitousFelicitousness 6375b42ff7 feat(model): register minimax h3 as a text2image base model
Reference entries for the bf16 repo and the sdnq uint4 quant load the
modular pipeline through the standard dispatch. Image tabs run the
model in still mode with audio off; the video tab keeps its own
overrides through the shared per-generation hook. Detailer is not
supported and is disabled with a warning.
2026-08-10 22:11:57 +01:00

336 lines
14 KiB
Python

import os
import time
import diffusers
from modules import shared, shared_items, errors, model_tools
from modules.logger import log
debug_load = os.environ.get('SD_LOAD_DEBUG', None)
def guess_by_size(fn, current_guess):
new_guess = None
if os.path.isfile(fn) and fn.endswith('.safetensors'):
size = round(os.path.getsize(fn) / 1024 / 1024)
if (size > 0 and size < 128):
log.warning(f'Model size smaller than expected: file="{fn}" size={size} MB')
elif (size >= 316 and size <= 324) or (size >= 156 and size <= 164): # 320 or 160
log.warning(f'Model detected as VAE model, but attempting to load as model: file="{fn}" size={size} MB')
new_guess = 'VAE'
elif (size >= 2002 and size <= 2038): # 2032
new_guess = 'Stable Diffusion 1.5'
elif (size >= 3138 and size <= 3142): #3140
new_guess = 'Stable Diffusion XL'
elif (size >= 3361 and size <= 3369): # 3368
new_guess = 'Stable Diffusion Upscale'
elif (size >= 4891 and size <= 4899): # 4897
new_guess = 'Stable Diffusion XL Inpaint'
elif (size >= 4970 and size <= 4976): # 4973
new_guess = 'Stable Diffusion 2' # SD v2 but could be eps or v-prediction
elif (size >= 5791 and size <= 5799): # 5795
new_guess = 'Stable Diffusion XL Refiner'
elif (size > 5692 and size < 5698) or (size > 4134 and size < 4138) or (size > 10362 and size < 10366) or (size > 15028 and size < 15228):
new_guess = 'Stable Diffusion 3'
elif (size >= 6420 and size <= 7220): # 6420, IustriousRedux is 6541, monkrenRealisticINT_v10 is 7217
new_guess = 'Stable Diffusion XL'
elif (size >= 9791 and size <= 9799): # 9794
new_guess = 'Stable Diffusion XL Instruct'
elif (size >= 18414 and size <= 18420): # sd35-large aio
new_guess = 'Stable Diffusion 3'
elif (size >= 20000 and size <= 40000):
new_guess = 'FLUX'
if debug_load:
log.trace(f'Autodetect: method=size file="{fn}" size={size} previous="{current_guess}" current="{new_guess}"')
return new_guess or current_guess
def guess_by_name(fn, current_guess):
new_guess = None
if 'instaflow' in fn.lower():
new_guess = 'InstaFlow'
elif 'segmoe' in fn.lower():
new_guess = 'SegMoE'
elif 'hunyuandit' in fn.lower():
new_guess = 'HunyuanDiT'
elif 'pixart-xl' in fn.lower():
new_guess = 'PixArtAlpha'
elif 'stable-diffusion-3' in fn.lower():
new_guess = 'Stable Diffusion 3'
elif 'stable-cascade' in fn.lower() or 'stablecascade' in fn.lower() or 'wuerstchen3' in fn.lower() or ('sotediffusion' in fn.lower() and "v2" in fn.lower()):
new_guess = 'Stable Cascade'
elif 'pixart-sigma' in fn.lower():
new_guess = 'PixArtSigma'
elif 'sana' in fn.lower():
new_guess = 'Sana'
elif 'lumina-next' in fn.lower():
new_guess = 'LuminaNext'
elif 'lumina-dimoo' in fn.lower():
new_guess = 'LuminaDiMOO'
elif 'lumina-image-2' in fn.lower():
new_guess = 'Lumina2'
elif 'kolors' in fn.lower():
new_guess = 'Kolors'
elif 'auraflow' in fn.lower() or 'pony-v7' in fn.lower():
new_guess = 'AuraFlow'
elif 'cogview3' in fn.lower():
new_guess = 'CogView3'
elif 'cogview4' in fn.lower():
new_guess = 'CogView4'
elif 'meissonic' in fn.lower():
new_guess = 'Meissonic'
elif 'omnigen2' in fn.lower():
new_guess = 'OmniGen2'
elif 'boogu' in fn.lower():
new_guess = 'Boogu'
elif 'omnigen' in fn.lower():
new_guess = 'OmniGen'
elif 'sd3' in fn.lower():
new_guess = 'Stable Diffusion 3'
elif 'hidream-o1' in fn.lower():
new_guess = 'HiDreamO1'
elif 'hidream' in fn.lower():
new_guess = 'HiDream'
elif 'zeta-chroma' in fn.lower() or 'zetachroma' in fn.lower():
new_guess = 'ZetaChroma'
elif 'chroma' in fn.lower() and 'xl' not in fn.lower():
new_guess = 'Chroma'
elif ('flux.2' in fn.lower() or 'f2' in fn.lower()) and 'klein' in fn.lower():
new_guess = 'FLUX2Klein'
elif 'flux.2' in fn.lower():
new_guess = 'FLUX2'
elif 'lens' in fn.lower():
new_guess = 'Lens'
elif 'ultraflux' in fn.lower():
new_guess = 'UltraFlux'
elif 'flux' in fn.lower() or 'flex.1' in fn.lower():
size = round(os.path.getsize(fn) / 1024 / 1024) if os.path.isfile(fn) else 0
if size > 11000 and size < 16000:
log.warning(f'Model detected as FLUX UNET model, but attempting to load a base model: file="{fn}" size={size} MB')
new_guess = 'FLUX'
elif 'flex.2' in fn.lower():
new_guess = 'FLEX'
elif fn.lower().endswith('anima') or 'anima-' in fn.lower():
new_guess = 'Anima'
elif 'cosmos-predict2' in fn.lower():
new_guess = 'Cosmos'
elif 'f-lite' in fn.lower():
new_guess = 'FLite'
elif 'minimax' in fn.lower():
new_guess = 'MiniMaxH3'
elif 'wan' in fn.lower():
new_guess = 'WanAI'
if 'chronoedit' in fn.lower():
new_guess = 'ChronoEdit'
elif 'bria' in fn.lower() or 'fibo' in fn.lower():
new_guess = 'Bria'
elif 'qwen' in fn.lower() or 'firered' in fn.lower() or 'unipic3' in fn.lower():
new_guess = 'Qwen'
elif 'nextstep' in fn.lower():
new_guess = 'NextStep'
elif 'kandinsky-2-1' in fn.lower():
new_guess = 'Kandinsky21'
elif 'kandinsky-2-2' in fn.lower():
new_guess = 'Kandinsky22'
elif 'kandinsky-3' in fn.lower():
new_guess = 'Kandinsky30'
elif 'kandinsky-5.0' in fn.lower():
new_guess = 'Kandinsky50'
elif 'hunyuanimage3' in fn.lower() or 'hunyuanimage-3' in fn.lower():
new_guess = 'HunyuanImage3'
elif 'hunyuanimage' in fn.lower():
new_guess = 'HunyuanImage'
elif 'x-omni' in fn.lower():
new_guess = 'XOmni'
elif 'sdxl-turbo' in fn.lower() or 'stable-diffusion-xl' in fn.lower():
new_guess = 'Stable Diffusion XL'
elif 'stable-video-diffusion' in fn.lower():
new_guess = 'StableVideoDiffusion'
elif 'prx-pixel' in fn.lower() or 'prxpixel' in fn.lower():
new_guess = 'PRXPixel'
elif 'prx-' in fn.lower():
new_guess = 'PRX'
elif 'gemini-' in fn.lower() and 'image' in fn.lower():
new_guess = 'NanoBanana'
elif 'ernie-image' in fn.lower():
new_guess = 'ERNIEImage'
elif 'nucleus-image' in fn.lower() or 'nucleusmoe-image' in fn.lower():
new_guess = 'NucleusImage'
elif 'z-image' in fn.lower() or 'z_image' in fn.lower() or 'zimage' in fn.lower():
new_guess = 'ZImage'
elif 'krea-2' in fn.lower() or 'krea2' in fn.lower():
new_guess = 'Krea2'
elif 'ideogram' in fn.lower():
new_guess = 'Ideogram4'
elif 'longcat-image' in fn.lower():
new_guess = 'LongCat'
elif 'ovis-image' in fn.lower():
new_guess = 'OvisImage'
elif 'glm-image' in fn.lower():
new_guess = 'GLMImage'
elif 'sdxs-1b' in fn.lower():
new_guess = 'SDXS'
elif 'step1x-edit' in fn.lower():
new_guess = 'Step1XEdit'
elif 'vibe-image-edit' in fn.lower():
new_guess = 'VIBE'
elif 'joyai-image-edit' in fn.lower() or 'joy-image-edit' in fn.lower():
new_guess = 'JoyEdit'
elif 'sefi-image' in fn.lower():
new_guess = 'SeFi'
elif 'mage-flow' in fn.lower():
new_guess = 'MageFlow'
if debug_load:
log.trace(f'Autodetect: method=name file="{fn}" previous="{current_guess}" current="{new_guess}"')
return new_guess or current_guess
def guess_by_diffusers(fn, current_guess):
exclude_by_name = ['ostris/Flex.2-preview', 'Owen777/UltraFlux-v1', './pretrain/FLUX.1-dev'] # pipeline may be misleading
if not os.path.isdir(fn):
return current_guess, None
index = os.path.join(fn, 'model_index.json')
if os.path.exists(index) and os.path.isfile(index):
index = shared.readfile(index, silent=True, as_type="dict")
name = index.get('_name_or_path', None)
if debug_load:
log.trace(f'Autodetect: method=diffusers file="{fn}" name="{name}"')
if (name is not None) and (name in exclude_by_name):
return current_guess, None
cls = index.get('_class_name', None)
if isinstance(cls, list):
cls = cls[-1]
pipeline = None
if cls is not None:
pipeline = getattr(diffusers, cls, None)
if pipeline is None:
pipeline = cls
if callable(pipeline):
is_quant = False
for folder in os.listdir(fn):
folder = os.path.join(fn, folder)
if is_quant:
break
if folder.endswith('quantization_config.json'):
is_quant = True
break
if folder.endswith('config.json'):
quantization_config = shared.readfile(folder, silent=True, as_type="dict").get("quantization_config", None)
if quantization_config is not None:
is_quant = True
break
if os.path.isdir(folder):
for f in os.listdir(folder):
f = os.path.join(folder, f)
if f.endswith('quantization_config.json'):
is_quant = True
break
if f.endswith('config.json'):
quantization_config = shared.readfile(f, silent=True, as_type="dict").get("quantization_config", None)
if quantization_config is not None:
is_quant = True
break
pipelines = shared_items.get_pipelines()
for k, v in pipelines.items():
if v is not None and v.__name__ == pipeline.__name__:
if is_quant:
k = f'{k} SDNQ'
if debug_load:
log.trace(f'Autodetect: method=diffusers file="{fn}" previous="{current_guess}" current="{k}"')
return k, v
return current_guess, None
def guess_variant(fn, current_guess):
new_guess = None
if 'inpaint' in fn.lower():
if current_guess == 'Stable Diffusion':
new_guess = 'Stable Diffusion Inpaint'
elif current_guess == 'Stable Diffusion XL':
new_guess = 'Stable Diffusion XL Inpaint'
elif 'instruct' in fn.lower():
if current_guess == 'Stable Diffusion':
new_guess = 'Stable Diffusion Instruct'
elif current_guess == 'Stable Diffusion XL':
new_guess = 'Stable Diffusion XL Instruct'
if debug_load:
log.trace(f'Autodetect: method=variant file="{fn}" previous="{current_guess}" current="{new_guess}"')
return new_guess or current_guess
def detect_pipeline(f: str, op: str = 'model'):
guess = shared.opts.diffusers_pipeline
pipeline = None
if guess == 'Autodetect':
try:
guess = 'Stable Diffusion XL' if ('XL' in f.upper() or 'SDNQ' in f.upper()) else 'Stable Diffusion' # set default guess
guess = guess_by_size(f, guess)
if debug_load:
log.trace(f'Autodetect: type=size guess="{guess}" file="{f}"')
guess = guess_by_name(f, guess)
if debug_load:
log.trace(f'Autodetect: type=name guess="{guess}" file="{f}"')
guess, pipeline = guess_by_diffusers(f, guess)
if debug_load:
log.trace(f'Autodetect: type=diffusers guess="{guess}" file="{f}"')
guess = guess_variant(f, guess)
if debug_load:
log.trace(f'Autodetect: type=variant guess="{guess}" file="{f}"')
pipeline = shared_items.get_pipelines().get(guess, None) if pipeline is None else pipeline
log.info(f'Autodetect {op}: detect="{guess}" class={getattr(pipeline, "__name__", None)} file="{f}"')
if debug_load is not None:
t0 = time.time()
keys = model_tools.get_safetensor_keys(f)
if keys is not None and len(keys) > 0:
modules = model_tools.list_to_dict(keys)
modules = model_tools.remove_entries_after_depth(modules, 3)
lst = model_tools.list_compact(keys)
t1 = time.time()
log.debug(f'Autodetect: modules={modules} list={lst} time={t1-t0:.2f}')
except Exception as e:
log.error(f'Autodetect {op}: file="{f}" {e}')
if debug_load:
errors.display(e, f'Load {op}: {f}')
return None, None
else:
try:
pipeline = shared_items.get_pipelines().get(guess, None) if pipeline is None else pipeline
log.info(f'Load {op}: detect="{guess}" class={getattr(pipeline, "__name__", None)} file="{f}"')
except Exception as e:
log.error(f'Load {op}: detect="{guess}" file="{f}" {e}')
if pipeline is None:
pipeline = diffusers.DiffusionPipeline
return pipeline, guess
def get_load_config(model_file, model_type, config_type='yaml'):
model_type = model_type.removesuffix(' SDNQ')
if config_type == 'yaml':
yaml = os.path.splitext(model_file)[0] + '.yaml'
if os.path.exists(yaml):
return yaml
if model_type == 'Stable Diffusion':
return 'configs/v1-inference.yaml'
if model_type == 'Stable Diffusion XL':
return 'configs/sd_xl_base.yaml'
if model_type == 'Stable Diffusion XL Refiner':
return 'configs/sd_xl_refiner.yaml'
if model_type == 'Stable Diffusion 2':
return None # dont know if its eps or v so let diffusers sort it out
# return 'configs/v2-inference-512-base.yaml'
# return 'configs/v2-inference-768-v.yaml'
elif config_type == 'json':
if not shared.opts.diffuser_cache_config:
return None
if model_type == 'Stable Diffusion':
return 'configs/sd15'
if model_type == 'Stable Diffusion XL':
return 'configs/sdxl'
if model_type == 'Stable Diffusion XL Refiner':
return 'configs/sdxl-refiner'
if model_type == 'Stable Diffusion 3':
return 'configs/sd3'
if model_type == 'FLUX':
return 'configs/flux'
return None