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 get_model_index(name: str): repo_id = name.removeprefix('Diffusers/') fn = None try: from huggingface_hub import hf_hub_download fn = hf_hub_download(repo_id, filename='model_index.json', cache_dir=shared.opts.diffusers_dir) log.debug(f'Autodetect model: repo="{repo_id}" index="{fn}"') return fn except Exception: pass return fn def guess_by_model_index(fn: str, name: str, current_guess: str): exclude_by_name = ['ostris/Flex.2-preview', 'Owen777/UltraFlux-v1', './pretrain/FLUX.1-dev'] # pipeline may be misleading index = os.path.join(fn, 'model_index.json') if (index is None) or (not os.path.exists(index)) or (not os.path.isfile(index)): if name is not None and name.startswith('Diffusers/'): index = get_model_index(name) if (index is None) or (not os.path.exists(index)) or (not os.path.isfile(index)): return current_guess, None 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(ckpt, op: str = 'model'): if isinstance(ckpt, str): f = ckpt name = None else: f = ckpt.path name = ckpt.name 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_model_index(f, name=name, current_guess=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