sd_models.py 13.0 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 114 115 116 117 118
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
    checkpoint_info = checkpoints_list.get(model_checkpoint, None)
    if checkpoint_info is not None:
        return checkpoint_info

    if len(checkpoints_list) == 0:
119
        print(f"No checkpoints found. When searching for checkpoints, looked at:", file=sys.stderr)
120 121 122 123 124
        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)
125 126
        print(f"Can't run without a checkpoint. Find and place a .ckpt file into any of those locations. The program will exit.", file=sys.stderr)
        exit(1)
127 128 129 130 131 132 133 134

    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


135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
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


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

    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
160

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

    return pl_sd
165 166


167 168 169 170 171 172 173 174 175 176 177 178 179 180
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


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

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

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

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

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

206
        if not shared.cmd_opts.no_half:
A
AUTOMATIC 已提交
207 208 209 210 211 212
            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

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

216 217
        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
218

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

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

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

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

235

J
Jay Smith 已提交
236 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
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

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

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

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

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

        # 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"))

303
    do_inpainting_hijack()
304

M
MrCheeze 已提交
305 306 307
    if shared.cmd_opts.no_half:
        sd_config.model.params.unet_config.params.use_fp16 = False

308
    sd_model = instantiate_from_config(sd_config.model)
309
    load_model_weights(sd_model, checkpoint_info)
310 311 312 313 314 315 316 317 318

    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()
319 320
    shared.sd_model = sd_model

321 322
    script_callbacks.model_loaded_callback(sd_model)

323 324 325 326
    print(f"Model loaded.")
    return sd_model


327
def reload_model_weights(sd_model=None, info=None):
328
    from modules import lowvram, devices, sd_hijack
329
    checkpoint_info = info or select_checkpoint()
330
 
331 332
    if not sd_model:
        sd_model = shared.sd_model
333

A
Aidan Holland 已提交
334
    if sd_model.sd_model_checkpoint == checkpoint_info.filename:
335 336
        return

337
    if sd_model.sd_checkpoint_info.config != checkpoint_info.config or should_hijack_inpainting(checkpoint_info) != should_hijack_inpainting(sd_model.sd_checkpoint_info):
338
        del sd_model
339
        checkpoints_loaded.clear()
340
        load_model(checkpoint_info)
341
        return shared.sd_model
342

343 344 345 346 347
    if shared.cmd_opts.lowvram or shared.cmd_opts.medvram:
        lowvram.send_everything_to_cpu()
    else:
        sd_model.to(devices.cpu)

348 349
    sd_hijack.model_hijack.undo_hijack(sd_model)

350
    load_model_weights(sd_model, checkpoint_info)
351

352
    sd_hijack.model_hijack.hijack(sd_model)
353
    script_callbacks.model_loaded_callback(sd_model)
354

355 356 357 358 359
    if not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram:
        sd_model.to(devices.device)

    print(f"Weights loaded.")
    return sd_model