CoolFace
Apppublic

declare-lab/tango2

sourceHugging Faceupdated 2y agoView on Hugging Face
92likes
loaders.py570 linesDownload Raw Back to diffusers
1# Copyright 2023 The HuggingFace Team. All rights reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14import os15from collections import defaultdict16from typing import Callable, Dict, List, Optional, Union17 18import torch19 20from .models.attention_processor import LoRAAttnProcessor21from .utils import (22    DIFFUSERS_CACHE,23    HF_HUB_OFFLINE,24    _get_model_file,25    deprecate,26    is_safetensors_available,27    is_transformers_available,28    logging,29)30 31 32if is_safetensors_available():33    import safetensors34 35if is_transformers_available():36    from transformers import PreTrainedModel, PreTrainedTokenizer37 38 39logger = logging.get_logger(__name__)40 41 42LORA_WEIGHT_NAME = "pytorch_lora_weights.bin"43LORA_WEIGHT_NAME_SAFE = "pytorch_lora_weights.safetensors"44 45TEXT_INVERSION_NAME = "learned_embeds.bin"46TEXT_INVERSION_NAME_SAFE = "learned_embeds.safetensors"47 48 49class AttnProcsLayers(torch.nn.Module):50    def __init__(self, state_dict: Dict[str, torch.Tensor]):51        super().__init__()52        self.layers = torch.nn.ModuleList(state_dict.values())53        self.mapping = dict(enumerate(state_dict.keys()))54        self.rev_mapping = {v: k for k, v in enumerate(state_dict.keys())}55 56        # we add a hook to state_dict() and load_state_dict() so that the57        # naming fits with `unet.attn_processors`58        def map_to(module, state_dict, *args, **kwargs):59            new_state_dict = {}60            for key, value in state_dict.items():61                num = int(key.split(".")[1])  # 0 is always "layers"62                new_key = key.replace(f"layers.{num}", module.mapping[num])63                new_state_dict[new_key] = value64 65            return new_state_dict66 67        def map_from(module, state_dict, *args, **kwargs):68            all_keys = list(state_dict.keys())69            for key in all_keys:70                replace_key = key.split(".processor")[0] + ".processor"71                new_key = key.replace(replace_key, f"layers.{module.rev_mapping[replace_key]}")72                state_dict[new_key] = state_dict[key]73                del state_dict[key]74 75        self._register_state_dict_hook(map_to)76        self._register_load_state_dict_pre_hook(map_from, with_module=True)77 78 79class UNet2DConditionLoadersMixin:80    def load_attn_procs(self, pretrained_model_name_or_path_or_dict: Union[str, Dict[str, torch.Tensor]], **kwargs):81        r"""82        Load pretrained attention processor layers into `UNet2DConditionModel`. Attention processor layers have to be83        defined in84        [cross_attention.py](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/cross_attention.py)85        and be a `torch.nn.Module` class.86 87        <Tip warning={true}>88 89            This function is experimental and might change in the future.90 91        </Tip>92 93        Parameters:94            pretrained_model_name_or_path_or_dict (`str` or `os.PathLike` or `dict`):95                Can be either:96 97                    - A string, the *model id* of a pretrained model hosted inside a model repo on huggingface.co.98                      Valid model ids should have an organization name, like `google/ddpm-celebahq-256`.99                    - A path to a *directory* containing model weights saved using [`~ModelMixin.save_config`], e.g.,100                      `./my_model_directory/`.101                    - A [torch state102                      dict](https://pytorch.org/tutorials/beginner/saving_loading_models.html#what-is-a-state-dict).103 104            cache_dir (`Union[str, os.PathLike]`, *optional*):105                Path to a directory in which a downloaded pretrained model configuration should be cached if the106                standard cache should not be used.107            force_download (`bool`, *optional*, defaults to `False`):108                Whether or not to force the (re-)download of the model weights and configuration files, overriding the109                cached versions if they exist.110            resume_download (`bool`, *optional*, defaults to `False`):111                Whether or not to delete incompletely received files. Will attempt to resume the download if such a112                file exists.113            proxies (`Dict[str, str]`, *optional*):114                A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',115                'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request.116            local_files_only(`bool`, *optional*, defaults to `False`):117                Whether or not to only look at local files (i.e., do not try to download the model).118            use_auth_token (`str` or *bool*, *optional*):119                The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated120                when running `diffusers-cli login` (stored in `~/.huggingface`).121            revision (`str`, *optional*, defaults to `"main"`):122                The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a123                git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any124                identifier allowed by git.125            subfolder (`str`, *optional*, defaults to `""`):126                In case the relevant files are located inside a subfolder of the model repo (either remote in127                huggingface.co or downloaded locally), you can specify the folder name here.128 129            mirror (`str`, *optional*):130                Mirror source to accelerate downloads in China. If you are from China and have an accessibility131                problem, you can set this option to resolve it. Note that we do not guarantee the timeliness or safety.132                Please refer to the mirror site for more information.133 134        <Tip>135 136         It is required to be logged in (`huggingface-cli login`) when you want to use private or [gated137         models](https://huggingface.co/docs/hub/models-gated#gated-models).138 139        </Tip>140        """141 142        cache_dir = kwargs.pop("cache_dir", DIFFUSERS_CACHE)143        force_download = kwargs.pop("force_download", False)144        resume_download = kwargs.pop("resume_download", False)145        proxies = kwargs.pop("proxies", None)146        local_files_only = kwargs.pop("local_files_only", HF_HUB_OFFLINE)147        use_auth_token = kwargs.pop("use_auth_token", None)148        revision = kwargs.pop("revision", None)149        subfolder = kwargs.pop("subfolder", None)150        weight_name = kwargs.pop("weight_name", None)151        use_safetensors = kwargs.pop("use_safetensors", None)152 153        if use_safetensors and not is_safetensors_available():154            raise ValueError(155                "`use_safetensors`=True but safetensors is not installed. Please install safetensors with `pip install safetenstors"156            )157 158        allow_pickle = False159        if use_safetensors is None:160            use_safetensors = is_safetensors_available()161            allow_pickle = True162 163        user_agent = {164            "file_type": "attn_procs_weights",165            "framework": "pytorch",166        }167 168        model_file = None169        if not isinstance(pretrained_model_name_or_path_or_dict, dict):170            # Let's first try to load .safetensors weights171            if (use_safetensors and weight_name is None) or (172                weight_name is not None and weight_name.endswith(".safetensors")173            ):174                try:175                    model_file = _get_model_file(176                        pretrained_model_name_or_path_or_dict,177                        weights_name=weight_name or LORA_WEIGHT_NAME_SAFE,178                        cache_dir=cache_dir,179                        force_download=force_download,180                        resume_download=resume_download,181                        proxies=proxies,182                        local_files_only=local_files_only,183                        use_auth_token=use_auth_token,184                        revision=revision,185                        subfolder=subfolder,186                        user_agent=user_agent,187                    )188                    state_dict = safetensors.torch.load_file(model_file, device="cpu")189                except IOError as e:190                    if not allow_pickle:191                        raise e192                    # try loading non-safetensors weights193                    pass194            if model_file is None:195                model_file = _get_model_file(196                    pretrained_model_name_or_path_or_dict,197                    weights_name=weight_name or LORA_WEIGHT_NAME,198                    cache_dir=cache_dir,199                    force_download=force_download,200                    resume_download=resume_download,201                    proxies=proxies,202                    local_files_only=local_files_only,203                    use_auth_token=use_auth_token,204                    revision=revision,205                    subfolder=subfolder,206                    user_agent=user_agent,207                )208                state_dict = torch.load(model_file, map_location="cpu")209        else:210            state_dict = pretrained_model_name_or_path_or_dict211 212        # fill attn processors213        attn_processors = {}214 215        is_lora = all("lora" in k for k in state_dict.keys())216 217        if is_lora:218            lora_grouped_dict = defaultdict(dict)219            for key, value in state_dict.items():220                attn_processor_key, sub_key = ".".join(key.split(".")[:-3]), ".".join(key.split(".")[-3:])221                lora_grouped_dict[attn_processor_key][sub_key] = value222 223            for key, value_dict in lora_grouped_dict.items():224                rank = value_dict["to_k_lora.down.weight"].shape[0]225                cross_attention_dim = value_dict["to_k_lora.down.weight"].shape[1]226                hidden_size = value_dict["to_k_lora.up.weight"].shape[0]227 228                attn_processors[key] = LoRAAttnProcessor(229                    hidden_size=hidden_size, cross_attention_dim=cross_attention_dim, rank=rank230                )231                attn_processors[key].load_state_dict(value_dict)232 233        else:234            raise ValueError(f"{model_file} does not seem to be in the correct format expected by LoRA training.")235 236        # set correct dtype & device237        attn_processors = {k: v.to(device=self.device, dtype=self.dtype) for k, v in attn_processors.items()}238 239        # set layers240        self.set_attn_processor(attn_processors)241 242    def save_attn_procs(243        self,244        save_directory: Union[str, os.PathLike],245        is_main_process: bool = True,246        weight_name: str = None,247        save_function: Callable = None,248        safe_serialization: bool = False,249        **kwargs,250    ):251        r"""252        Save an attention processor to a directory, so that it can be re-loaded using the253        `[`~loaders.UNet2DConditionLoadersMixin.load_attn_procs`]` method.254 255        Arguments:256            save_directory (`str` or `os.PathLike`):257                Directory to which to save. Will be created if it doesn't exist.258            is_main_process (`bool`, *optional*, defaults to `True`):259                Whether the process calling this is the main process or not. Useful when in distributed training like260                TPUs and need to call this function on all processes. In this case, set `is_main_process=True` only on261                the main process to avoid race conditions.262            save_function (`Callable`):263                The function to use to save the state dictionary. Useful on distributed training like TPUs when one264                need to replace `torch.save` by another method. Can be configured with the environment variable265                `DIFFUSERS_SAVE_MODE`.266        """267        weight_name = weight_name or deprecate(268            "weights_name",269            "0.18.0",270            "`weights_name` is deprecated, please use `weight_name` instead.",271            take_from=kwargs,272        )273        if os.path.isfile(save_directory):274            logger.error(f"Provided path ({save_directory}) should be a directory, not a file")275            return276 277        if save_function is None:278            if safe_serialization:279 280                def save_function(weights, filename):281                    return safetensors.torch.save_file(weights, filename, metadata={"format": "pt"})282 283            else:284                save_function = torch.save285 286        os.makedirs(save_directory, exist_ok=True)287 288        model_to_save = AttnProcsLayers(self.attn_processors)289 290        # Save the model291        state_dict = model_to_save.state_dict()292 293        if weight_name is None:294            if safe_serialization:295                weight_name = LORA_WEIGHT_NAME_SAFE296            else:297                weight_name = LORA_WEIGHT_NAME298 299        # Save the model300        save_function(state_dict, os.path.join(save_directory, weight_name))301        logger.info(f"Model weights saved in {os.path.join(save_directory, weight_name)}")302 303 304class TextualInversionLoaderMixin:305    r"""306    Mixin class for loading textual inversion tokens and embeddings to the tokenizer and text encoder.307    """308 309    def maybe_convert_prompt(self, prompt: Union[str, List[str]], tokenizer: "PreTrainedTokenizer"):310        r"""311        Maybe convert a prompt into a "multi vector"-compatible prompt. If the prompt includes a token that corresponds312        to a multi-vector textual inversion embedding, this function will process the prompt so that the special token313        is replaced with multiple special tokens each corresponding to one of the vectors. If the prompt has no textual314        inversion token or a textual inversion token that is a single vector, the input prompt is simply returned.315 316        Parameters:317            prompt (`str` or list of `str`):318                The prompt or prompts to guide the image generation.319            tokenizer (`PreTrainedTokenizer`):320                The tokenizer responsible for encoding the prompt into input tokens.321 322        Returns:323            `str` or list of `str`: The converted prompt324        """325        if not isinstance(prompt, List):326            prompts = [prompt]327        else:328            prompts = prompt329 330        prompts = [self._maybe_convert_prompt(p, tokenizer) for p in prompts]331 332        if not isinstance(prompt, List):333            return prompts[0]334 335        return prompts336 337    def _maybe_convert_prompt(self, prompt: str, tokenizer: "PreTrainedTokenizer"):338        r"""339        Maybe convert a prompt into a "multi vector"-compatible prompt. If the prompt includes a token that corresponds340        to a multi-vector textual inversion embedding, this function will process the prompt so that the special token341        is replaced with multiple special tokens each corresponding to one of the vectors. If the prompt has no textual342        inversion token or a textual inversion token that is a single vector, the input prompt is simply returned.343 344        Parameters:345            prompt (`str`):346                The prompt to guide the image generation.347            tokenizer (`PreTrainedTokenizer`):348                The tokenizer responsible for encoding the prompt into input tokens.349 350        Returns:351            `str`: The converted prompt352        """353        tokens = tokenizer.tokenize(prompt)354        for token in tokens:355            if token in tokenizer.added_tokens_encoder:356                replacement = token357                i = 1358                while f"{token}_{i}" in tokenizer.added_tokens_encoder:359                    replacement += f"{token}_{i}"360                    i += 1361 362                prompt = prompt.replace(token, replacement)363 364        return prompt365 366    def load_textual_inversion(367        self, pretrained_model_name_or_path: Union[str, Dict[str, torch.Tensor]], token: Optional[str] = None, **kwargs368    ):369        r"""370        Load textual inversion embeddings into the text encoder of stable diffusion pipelines. Both `diffusers` and371        `Automatic1111` formats are supported.372 373        <Tip warning={true}>374 375            This function is experimental and might change in the future.376 377        </Tip>378 379        Parameters:380             pretrained_model_name_or_path (`str` or `os.PathLike`):381                Can be either:382 383                    - A string, the *model id* of a pretrained model hosted inside a model repo on huggingface.co.384                      Valid model ids should have an organization name, like385                      `"sd-concepts-library/low-poly-hd-logos-icons"`.386                    - A path to a *directory* containing textual inversion weights, e.g.387                      `./my_text_inversion_directory/`.388            weight_name (`str`, *optional*):389                Name of a custom weight file. This should be used in two cases:390 391                    - The saved textual inversion file is in `diffusers` format, but was saved under a specific weight392                      name, such as `text_inv.bin`.393                    - The saved textual inversion file is in the "Automatic1111" form.394            cache_dir (`Union[str, os.PathLike]`, *optional*):395                Path to a directory in which a downloaded pretrained model configuration should be cached if the396                standard cache should not be used.397            force_download (`bool`, *optional*, defaults to `False`):398                Whether or not to force the (re-)download of the model weights and configuration files, overriding the399                cached versions if they exist.400            resume_download (`bool`, *optional*, defaults to `False`):401                Whether or not to delete incompletely received files. Will attempt to resume the download if such a402                file exists.403            proxies (`Dict[str, str]`, *optional*):404                A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',405                'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request.406            local_files_only(`bool`, *optional*, defaults to `False`):407                Whether or not to only look at local files (i.e., do not try to download the model).408            use_auth_token (`str` or *bool*, *optional*):409                The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated410                when running `diffusers-cli login` (stored in `~/.huggingface`).411            revision (`str`, *optional*, defaults to `"main"`):412                The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a413                git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any414                identifier allowed by git.415            subfolder (`str`, *optional*, defaults to `""`):416                In case the relevant files are located inside a subfolder of the model repo (either remote in417                huggingface.co or downloaded locally), you can specify the folder name here.418 419            mirror (`str`, *optional*):420                Mirror source to accelerate downloads in China. If you are from China and have an accessibility421                problem, you can set this option to resolve it. Note that we do not guarantee the timeliness or safety.422                Please refer to the mirror site for more information.423 424        <Tip>425 426         It is required to be logged in (`huggingface-cli login`) when you want to use private or [gated427         models](https://huggingface.co/docs/hub/models-gated#gated-models).428 429        </Tip>430        """431        if not hasattr(self, "tokenizer") or not isinstance(self.tokenizer, PreTrainedTokenizer):432            raise ValueError(433                f"{self.__class__.__name__} requires `self.tokenizer` of type `PreTrainedTokenizer` for calling"434                f" `{self.load_textual_inversion.__name__}`"435            )436 437        if not hasattr(self, "text_encoder") or not isinstance(self.text_encoder, PreTrainedModel):438            raise ValueError(439                f"{self.__class__.__name__} requires `self.text_encoder` of type `PreTrainedModel` for calling"440                f" `{self.load_textual_inversion.__name__}`"441            )442 443        cache_dir = kwargs.pop("cache_dir", DIFFUSERS_CACHE)444        force_download = kwargs.pop("force_download", False)445        resume_download = kwargs.pop("resume_download", False)446        proxies = kwargs.pop("proxies", None)447        local_files_only = kwargs.pop("local_files_only", HF_HUB_OFFLINE)448        use_auth_token = kwargs.pop("use_auth_token", None)449        revision = kwargs.pop("revision", None)450        subfolder = kwargs.pop("subfolder", None)451        weight_name = kwargs.pop("weight_name", None)452        use_safetensors = kwargs.pop("use_safetensors", None)453 454        if use_safetensors and not is_safetensors_available():455            raise ValueError(456                "`use_safetensors`=True but safetensors is not installed. Please install safetensors with `pip install safetenstors"457            )458 459        allow_pickle = False460        if use_safetensors is None:461            use_safetensors = is_safetensors_available()462            allow_pickle = True463 464        user_agent = {465            "file_type": "text_inversion",466            "framework": "pytorch",467        }468 469        # 1. Load textual inversion file470        model_file = None471        # Let's first try to load .safetensors weights472        if (use_safetensors and weight_name is None) or (473            weight_name is not None and weight_name.endswith(".safetensors")474        ):475            try:476                model_file = _get_model_file(477                    pretrained_model_name_or_path,478                    weights_name=weight_name or TEXT_INVERSION_NAME_SAFE,479                    cache_dir=cache_dir,480                    force_download=force_download,481                    resume_download=resume_download,482                    proxies=proxies,483                    local_files_only=local_files_only,484                    use_auth_token=use_auth_token,485                    revision=revision,486                    subfolder=subfolder,487                    user_agent=user_agent,488                )489                state_dict = safetensors.torch.load_file(model_file, device="cpu")490            except Exception as e:491                if not allow_pickle:492                    raise e493 494                model_file = None495 496        if model_file is None:497            model_file = _get_model_file(498                pretrained_model_name_or_path,499                weights_name=weight_name or TEXT_INVERSION_NAME,500                cache_dir=cache_dir,501                force_download=force_download,502                resume_download=resume_download,503                proxies=proxies,504                local_files_only=local_files_only,505                use_auth_token=use_auth_token,506                revision=revision,507                subfolder=subfolder,508                user_agent=user_agent,509            )510            state_dict = torch.load(model_file, map_location="cpu")511 512        # 2. Load token and embedding correcly from file513        if isinstance(state_dict, torch.Tensor):514            if token is None:515                raise ValueError(516                    "You are trying to load a textual inversion embedding that has been saved as a PyTorch tensor. Make sure to pass the name of the corresponding token in this case: `token=...`."517                )518            embedding = state_dict519        elif len(state_dict) == 1:520            # diffusers521            loaded_token, embedding = next(iter(state_dict.items()))522        elif "string_to_param" in state_dict:523            # A1111524            loaded_token = state_dict["name"]525            embedding = state_dict["string_to_param"]["*"]526 527        if token is not None and loaded_token != token:528            logger.warn(f"The loaded token: {loaded_token} is overwritten by the passed token {token}.")529        else:530            token = loaded_token531 532        embedding = embedding.to(dtype=self.text_encoder.dtype, device=self.text_encoder.device)533 534        # 3. Make sure we don't mess up the tokenizer or text encoder535        vocab = self.tokenizer.get_vocab()536        if token in vocab:537            raise ValueError(538                f"Token {token} already in tokenizer vocabulary. Please choose a different token name or remove {token} and embedding from the tokenizer and text encoder."539            )540        elif f"{token}_1" in vocab:541            multi_vector_tokens = [token]542            i = 1543            while f"{token}_{i}" in self.tokenizer.added_tokens_encoder:544                multi_vector_tokens.append(f"{token}_{i}")545                i += 1546 547            raise ValueError(548                f"Multi-vector Token {multi_vector_tokens} already in tokenizer vocabulary. Please choose a different token name or remove the {multi_vector_tokens} and embedding from the tokenizer and text encoder."549            )550 551        is_multi_vector = len(embedding.shape) > 1 and embedding.shape[0] > 1552 553        if is_multi_vector:554            tokens = [token] + [f"{token}_{i}" for i in range(1, embedding.shape[0])]555            embeddings = [e for e in embedding]  # noqa: C416556        else:557            tokens = [token]558            embeddings = [embedding[0]] if len(embedding.shape) > 1 else [embedding]559 560        # add tokens and get ids561        self.tokenizer.add_tokens(tokens)562        token_ids = self.tokenizer.convert_tokens_to_ids(tokens)563 564        # resize token embeddings and set new embeddings565        self.text_encoder.resize_token_embeddings(len(self.tokenizer))566        for token_id, embedding in zip(token_ids, embeddings):567            self.text_encoder.get_input_embeddings().weight.data[token_id] = embedding568 569        logger.info("Loaded textual inversion embedding for {token}.")570