sd_models.py 13.1 KB
Newer Older
1
import collections
2 3
import os.path
import sys
4
import gc
5 6
from collections import namedtuple
import torch
7
import re
A
AUTOMATIC 已提交
8
import safetensors.torch
9
from omegaconf import OmegaConf
J
Jay Smith 已提交
10 11 12
from os import mkdir
from urllib import request
import ldm.modules.midas as midas
13 14 15

from ldm.util import instantiate_from_config

M
Muhammad Rizqi Nur 已提交
16
from modules import shared, modelloader, devices, script_callbacks, sd_vae
17
from modules.paths import models_path
18
from modules.sd_hijack_inpainting import do_inpainting_hijack, should_hijack_inpainting
19 20

model_dir = "Stable-diffusion"
21
model_path = os.path.abspath(os.path.join(models_path, model_dir))
22

23
CheckpointInfo = namedtuple("CheckpointInfo", ['filename', 'title', 'hash', 'model_name', 'config'])
24
checkpoints_list = {}
25
checkpoints_loaded = collections.OrderedDict()
26 27 28 29

try:
    # this silences the annoying "Some weights of the model checkpoint were not used when initializing..." message at start.

M
MalumaDev 已提交
30
    from transformers import logging, CLIPModel
31 32 33 34 35 36

    logging.set_verbosity_error()
except Exception:
    pass


37
def setup_model():
38 39
    if not os.path.exists(model_path):
        os.makedirs(model_path)
40

41
    list_models()
J
Jay Smith 已提交
42
    enable_midas_autodownload()
43 44


45 46 47 48
def checkpoint_tiles(): 
    convert = lambda name: int(name) if name.isdigit() else name.lower() 
    alphanumeric_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] 
    return sorted([x.title for x in checkpoints_list.values()], key = alphanumeric_key)
49 50


51 52
def list_models():
    checkpoints_list.clear()
N
Nicolas Patry 已提交
53
    model_list = modelloader.load_models(model_path=model_path, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"])
54

55
    def modeltitle(path, shorthash):
56 57
        abspath = os.path.abspath(path)

58 59
        if shared.cmd_opts.ckpt_dir is not None and abspath.startswith(shared.cmd_opts.ckpt_dir):
            name = abspath.replace(shared.cmd_opts.ckpt_dir, '')
60 61
        elif abspath.startswith(model_path):
            name = abspath.replace(model_path, '')
62 63 64 65 66 67
        else:
            name = os.path.basename(path)

        if name.startswith("\\") or name.startswith("/"):
            name = name[1:]

68 69
        shortname = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]

70
        return f'{name} [{shorthash}]', shortname
71 72 73 74

    cmd_ckpt = shared.cmd_opts.ckpt
    if os.path.exists(cmd_ckpt):
        h = model_hash(cmd_ckpt)
75
        title, short_model_name = modeltitle(cmd_ckpt, h)
76
        checkpoints_list[title] = CheckpointInfo(cmd_ckpt, title, h, short_model_name, shared.cmd_opts.config)
77
        shared.opts.data['sd_model_checkpoint'] = title
78
    elif cmd_ckpt is not None and cmd_ckpt != shared.default_sd_model_file:
79 80 81
        print(f"Checkpoint in --ckpt argument not found (Possible it was moved to {model_path}: {cmd_ckpt}", file=sys.stderr)
    for filename in model_list:
        h = model_hash(filename)
82
        title, short_model_name = modeltitle(filename, h)
83 84 85 86 87 88 89

        basename, _ = os.path.splitext(filename)
        config = basename + ".yaml"
        if not os.path.exists(config):
            config = shared.cmd_opts.config

        checkpoints_list[title] = CheckpointInfo(filename, title, h, short_model_name, config)
90

91

D
DepFA 已提交
92
def get_closet_checkpoint_match(searchString):
D
DepFA 已提交
93
    applicable = sorted([info for info in checkpoints_list.values() if searchString in info.title], key = lambda x:len(x.title))
94
    if len(applicable) > 0:
D
DepFA 已提交
95 96
        return applicable[0]
    return None
97

98

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
def model_hash(filename):
    try:
        with open(filename, "rb") as file:
            import hashlib
            m = hashlib.sha256()

            file.seek(0x100000)
            m.update(file.read(0x10000))
            return m.hexdigest()[0:8]
    except FileNotFoundError:
        return 'NOFILE'


def select_checkpoint():
    model_checkpoint = shared.opts.sd_model_checkpoint
114
        
115 116 117 118 119
    checkpoint_info = checkpoints_list.get(model_checkpoint, None)
    if checkpoint_info is not None:
        return checkpoint_info

    if len(checkpoints_list) == 0:
120
        print("No checkpoints found. When searching for checkpoints, looked at:", file=sys.stderr)
121 122 123 124 125
        if shared.cmd_opts.ckpt is not None:
            print(f" - file {os.path.abspath(shared.cmd_opts.ckpt)}", file=sys.stderr)
        print(f" - directory {model_path}", file=sys.stderr)
        if shared.cmd_opts.ckpt_dir is not None:
            print(f" - directory {os.path.abspath(shared.cmd_opts.ckpt_dir)}", file=sys.stderr)
126
        print("Can't run without a checkpoint. Find and place a .ckpt file into any of those locations. The program will exit.", file=sys.stderr)
127
        exit(1)
128 129 130 131 132 133 134 135

    checkpoint_info = next(iter(checkpoints_list.values()))
    if model_checkpoint is not None:
        print(f"Checkpoint {model_checkpoint} not found; loading fallback {checkpoint_info.title}", file=sys.stderr)

    return checkpoint_info


136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
chckpoint_dict_replacements = {
    'cond_stage_model.transformer.embeddings.': 'cond_stage_model.transformer.text_model.embeddings.',
    'cond_stage_model.transformer.encoder.': 'cond_stage_model.transformer.text_model.encoder.',
    'cond_stage_model.transformer.final_layer_norm.': 'cond_stage_model.transformer.text_model.final_layer_norm.',
}


def transform_checkpoint_dict_key(k):
    for text, replacement in chckpoint_dict_replacements.items():
        if k.startswith(text):
            k = replacement + k[len(text):]

    return k


151
def get_state_dict_from_checkpoint(pl_sd):
152 153
    pl_sd = pl_sd.pop("state_dict", pl_sd)
    pl_sd.pop("state_dict", None)
154 155 156 157 158 159 160

    sd = {}
    for k, v in pl_sd.items():
        new_key = transform_checkpoint_dict_key(k)

        if new_key is not None:
            sd[new_key] = v
161

A
AUTOMATIC 已提交
162 163 164 165
    pl_sd.clear()
    pl_sd.update(sd)

    return pl_sd
166 167


168 169 170 171 172 173 174 175 176 177 178 179 180 181
def read_state_dict(checkpoint_file, print_global_state=False, map_location=None):
    _, extension = os.path.splitext(checkpoint_file)
    if extension.lower() == ".safetensors":
        pl_sd = safetensors.torch.load_file(checkpoint_file, device=map_location or shared.weight_load_location)
    else:
        pl_sd = torch.load(checkpoint_file, map_location=map_location or shared.weight_load_location)

    if print_global_state and "global_step" in pl_sd:
        print(f"Global Step: {pl_sd['global_step']}")

    sd = get_state_dict_from_checkpoint(pl_sd)
    return sd


182
def load_model_weights(model, checkpoint_info, vae_file="auto"):
183 184 185
    checkpoint_file = checkpoint_info.filename
    sd_model_hash = checkpoint_info.hash

C
cluder 已提交
186 187 188 189
    cache_enabled = shared.opts.sd_checkpoint_cache > 0

    if cache_enabled and checkpoint_info in checkpoints_loaded:
        # use checkpoint cache
190
        print(f"Loading weights [{sd_model_hash}] from cache")
C
cluder 已提交
191 192 193
        model.load_state_dict(checkpoints_loaded[checkpoint_info])
    else:
        # load from file
194
        print(f"Loading weights [{sd_model_hash}] from {checkpoint_file}")
195

196
        sd = read_state_dict(checkpoint_file)
197 198
        model.load_state_dict(sd, strict=False)
        del sd
C
cluder 已提交
199 200 201 202
        
        if cache_enabled:
            # cache newly loaded model
            checkpoints_loaded[checkpoint_info] = model.state_dict().copy()
203

204 205
        if shared.cmd_opts.opt_channelslast:
            model.to(memory_format=torch.channels_last)
206

207
        if not shared.cmd_opts.no_half:
A
AUTOMATIC 已提交
208 209 210 211 212 213
            vae = model.first_stage_model

            # with --no-half-vae, remove VAE from model when doing half() to prevent its weights from being converted to float16
            if shared.cmd_opts.no_half_vae:
                model.first_stage_model = None

214
            model.half()
A
AUTOMATIC 已提交
215
            model.first_stage_model = vae
216

217 218
        devices.dtype = torch.float32 if shared.cmd_opts.no_half else torch.float16
        devices.dtype_vae = torch.float32 if shared.cmd_opts.no_half or shared.cmd_opts.no_half_vae else torch.float16
219

220
        model.first_stage_model.to(devices.dtype_vae)
A
AUTOMATIC 已提交
221

C
cluder 已提交
222 223
    # clean up cache if limit is reached
    if cache_enabled:
C
cluder 已提交
224
        while len(checkpoints_loaded) > shared.opts.sd_checkpoint_cache + 1: # we need to count the current model
M
Muhammad Rizqi Nur 已提交
225 226
            checkpoints_loaded.popitem(last=False)  # LRU

227
    model.sd_model_hash = sd_model_hash
A
Aidan Holland 已提交
228
    model.sd_model_checkpoint = checkpoint_file
229
    model.sd_checkpoint_info = checkpoint_info
230

M
Misc  
Muhammad Rizqi Nur 已提交
231
    sd_vae.delete_base_vae()
232
    sd_vae.clear_loaded_vae()
233
    vae_file = sd_vae.resolve_vae(checkpoint_file, vae_file=vae_file)
234 235
    sd_vae.load_vae(model, vae_file)

236

J
Jay Smith 已提交
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
def enable_midas_autodownload():
    """
    Gives the ldm.modules.midas.api.load_model function automatic downloading.

    When the 512-depth-ema model, and other future models like it, is loaded,
    it calls midas.api.load_model to load the associated midas depth model.
    This function applies a wrapper to download the model to the correct
    location automatically.
    """

    midas_path = os.path.join(models_path, 'midas')

    # stable-diffusion-stability-ai hard-codes the midas model path to
    # a location that differs from where other scripts using this model look.
    # HACK: Overriding the path here.
    for k, v in midas.api.ISL_PATHS.items():
        file_name = os.path.basename(v)
        midas.api.ISL_PATHS[k] = os.path.join(midas_path, file_name)

    midas_urls = {
        "dpt_large": "https://github.com/intel-isl/DPT/releases/download/1_0/dpt_large-midas-2f21e586.pt",
        "dpt_hybrid": "https://github.com/intel-isl/DPT/releases/download/1_0/dpt_hybrid-midas-501f0c75.pt",
        "midas_v21": "https://github.com/AlexeyAB/MiDaS/releases/download/midas_dpt/midas_v21-f6b98070.pt",
        "midas_v21_small": "https://github.com/AlexeyAB/MiDaS/releases/download/midas_dpt/midas_v21_small-70d6b9c8.pt",
    }

    midas.api.load_model_inner = midas.api.load_model

    def load_model_wrapper(model_type):
        path = midas.api.ISL_PATHS[model_type]
        if not os.path.exists(path):
            if not os.path.exists(midas_path):
                mkdir(midas_path)
    
            print(f"Downloading midas model weights for {model_type} to {path}")
            request.urlretrieve(midas_urls[model_type], path)
            print(f"{model_type} downloaded")

        return midas.api.load_model_inner(model_type)

    midas.api.load_model = load_model_wrapper

279
def load_model(checkpoint_info=None):
280
    from modules import lowvram, sd_hijack
281
    checkpoint_info = checkpoint_info or select_checkpoint()
282

283
    if checkpoint_info.config != shared.cmd_opts.config:
284
        print(f"Loading config from: {checkpoint_info.config}")
285

286 287 288 289 290 291
    if shared.sd_model:
        sd_hijack.model_hijack.undo_hijack(shared.sd_model)
        shared.sd_model = None
        gc.collect()
        devices.torch_gc()

292
    sd_config = OmegaConf.load(checkpoint_info.config)
293 294 295 296 297 298
    
    if should_hijack_inpainting(checkpoint_info):
        # Hardcoded config for now...
        sd_config.model.target = "ldm.models.diffusion.ddpm.LatentInpaintDiffusion"
        sd_config.model.params.conditioning_key = "hybrid"
        sd_config.model.params.unet_config.params.in_channels = 9
299
        sd_config.model.params.finetune_keys = None
300 301 302 303

        # Create a "fake" config with a different name so that we know to unload it when switching models.
        checkpoint_info = checkpoint_info._replace(config=checkpoint_info.config.replace(".yaml", "-inpainting.yaml"))

304 305 306
    if not hasattr(sd_config.model.params, "use_ema"):
        sd_config.model.params.use_ema = False

307
    do_inpainting_hijack()
308

M
MrCheeze 已提交
309 310 311
    if shared.cmd_opts.no_half:
        sd_config.model.params.unet_config.params.use_fp16 = False

312
    sd_model = instantiate_from_config(sd_config.model)
313
    load_model_weights(sd_model, checkpoint_info)
314 315 316 317 318 319 320 321 322

    if shared.cmd_opts.lowvram or shared.cmd_opts.medvram:
        lowvram.setup_for_low_vram(sd_model, shared.cmd_opts.medvram)
    else:
        sd_model.to(shared.device)

    sd_hijack.model_hijack.hijack(sd_model)

    sd_model.eval()
323 324
    shared.sd_model = sd_model

325 326
    script_callbacks.model_loaded_callback(sd_model)

327
    print("Model loaded.")
328 329 330
    return sd_model


331
def reload_model_weights(sd_model=None, info=None):
332
    from modules import lowvram, devices, sd_hijack
333
    checkpoint_info = info or select_checkpoint()
334
 
335 336
    if not sd_model:
        sd_model = shared.sd_model
337

A
Aidan Holland 已提交
338
    if sd_model.sd_model_checkpoint == checkpoint_info.filename:
339 340
        return

341
    if sd_model.sd_checkpoint_info.config != checkpoint_info.config or should_hijack_inpainting(checkpoint_info) != should_hijack_inpainting(sd_model.sd_checkpoint_info):
342
        del sd_model
343
        checkpoints_loaded.clear()
344
        load_model(checkpoint_info)
345
        return shared.sd_model
346

347 348 349 350 351
    if shared.cmd_opts.lowvram or shared.cmd_opts.medvram:
        lowvram.send_everything_to_cpu()
    else:
        sd_model.to(devices.cpu)

352 353
    sd_hijack.model_hijack.undo_hijack(sd_model)

354
    load_model_weights(sd_model, checkpoint_info)
355

356
    sd_hijack.model_hijack.hijack(sd_model)
357
    script_callbacks.model_loaded_callback(sd_model)
358

359 360 361
    if not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram:
        sd_model.to(devices.device)

362
    print("Weights loaded.")
363
    return sd_model