CoolFace
Apppublic

tsi-org/tango

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
checkpoint_merger.py287 linesDownload Raw Back to community
1import glob2import os3from typing import Dict, List, Union4 5import torch6 7from diffusers.utils import is_safetensors_available8 9 10if is_safetensors_available():11    import safetensors.torch12 13from huggingface_hub import snapshot_download14 15from diffusers import DiffusionPipeline, __version__16from diffusers.schedulers.scheduling_utils import SCHEDULER_CONFIG_NAME17from diffusers.utils import CONFIG_NAME, DIFFUSERS_CACHE, ONNX_WEIGHTS_NAME, WEIGHTS_NAME18 19 20class CheckpointMergerPipeline(DiffusionPipeline):21    """22    A class that that supports merging diffusion models based on the discussion here:23    https://github.com/huggingface/diffusers/issues/87724 25    Example usage:-26 27    pipe = DiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", custom_pipeline="checkpoint_merger.py")28 29    merged_pipe = pipe.merge(["CompVis/stable-diffusion-v1-4","prompthero/openjourney"], interp = 'inv_sigmoid', alpha = 0.8, force = True)30 31    merged_pipe.to('cuda')32 33    prompt = "An astronaut riding a unicycle on Mars"34 35    results = merged_pipe(prompt)36 37    ## For more details, see the docstring for the merge method.38 39    """40 41    def __init__(self):42        self.register_to_config()43        super().__init__()44 45    def _compare_model_configs(self, dict0, dict1):46        if dict0 == dict1:47            return True48        else:49            config0, meta_keys0 = self._remove_meta_keys(dict0)50            config1, meta_keys1 = self._remove_meta_keys(dict1)51            if config0 == config1:52                print(f"Warning !: Mismatch in keys {meta_keys0} and {meta_keys1}.")53                return True54        return False55 56    def _remove_meta_keys(self, config_dict: Dict):57        meta_keys = []58        temp_dict = config_dict.copy()59        for key in config_dict.keys():60            if key.startswith("_"):61                temp_dict.pop(key)62                meta_keys.append(key)63        return (temp_dict, meta_keys)64 65    @torch.no_grad()66    def merge(self, pretrained_model_name_or_path_list: List[Union[str, os.PathLike]], **kwargs):67        """68        Returns a new pipeline object of the class 'DiffusionPipeline' with the merged checkpoints(weights) of the models passed69        in the argument 'pretrained_model_name_or_path_list' as a list.70 71        Parameters:72        -----------73            pretrained_model_name_or_path_list : A list of valid pretrained model names in the HuggingFace hub or paths to locally stored models in the HuggingFace format.74 75            **kwargs:76                Supports all the default DiffusionPipeline.get_config_dict kwargs viz..77 78                cache_dir, resume_download, force_download, proxies, local_files_only, use_auth_token, revision, torch_dtype, device_map.79 80                alpha - The interpolation parameter. Ranges from 0 to 1.  It affects the ratio in which the checkpoints are merged. A 0.8 alpha81                    would mean that the first model checkpoints would affect the final result far less than an alpha of 0.282 83                interp - The interpolation method to use for the merging. Supports "sigmoid", "inv_sigmoid", "add_diff" and None.84                    Passing None uses the default interpolation which is weighted sum interpolation. For merging three checkpoints, only "add_diff" is supported.85 86                force - Whether to ignore mismatch in model_config.json for the current models. Defaults to False.87 88        """89        # Default kwargs from DiffusionPipeline90        cache_dir = kwargs.pop("cache_dir", DIFFUSERS_CACHE)91        resume_download = kwargs.pop("resume_download", False)92        force_download = kwargs.pop("force_download", False)93        proxies = kwargs.pop("proxies", None)94        local_files_only = kwargs.pop("local_files_only", False)95        use_auth_token = kwargs.pop("use_auth_token", None)96        revision = kwargs.pop("revision", None)97        torch_dtype = kwargs.pop("torch_dtype", None)98        device_map = kwargs.pop("device_map", None)99 100        alpha = kwargs.pop("alpha", 0.5)101        interp = kwargs.pop("interp", None)102 103        print("Received list", pretrained_model_name_or_path_list)104        print(f"Combining with alpha={alpha}, interpolation mode={interp}")105 106        checkpoint_count = len(pretrained_model_name_or_path_list)107        # Ignore result from model_index_json comparision of the two checkpoints108        force = kwargs.pop("force", False)109 110        # If less than 2 checkpoints, nothing to merge. If more than 3, not supported for now.111        if checkpoint_count > 3 or checkpoint_count < 2:112            raise ValueError(113                "Received incorrect number of checkpoints to merge. Ensure that either 2 or 3 checkpoints are being"114                " passed."115            )116 117        print("Received the right number of checkpoints")118        # chkpt0, chkpt1 = pretrained_model_name_or_path_list[0:2]119        # chkpt2 = pretrained_model_name_or_path_list[2] if checkpoint_count == 3 else None120 121        # Validate that the checkpoints can be merged122        # Step 1: Load the model config and compare the checkpoints. We'll compare the model_index.json first while ignoring the keys starting with '_'123        config_dicts = []124        for pretrained_model_name_or_path in pretrained_model_name_or_path_list:125            config_dict = DiffusionPipeline.load_config(126                pretrained_model_name_or_path,127                cache_dir=cache_dir,128                resume_download=resume_download,129                force_download=force_download,130                proxies=proxies,131                local_files_only=local_files_only,132                use_auth_token=use_auth_token,133                revision=revision,134            )135            config_dicts.append(config_dict)136 137        comparison_result = True138        for idx in range(1, len(config_dicts)):139            comparison_result &= self._compare_model_configs(config_dicts[idx - 1], config_dicts[idx])140            if not force and comparison_result is False:141                raise ValueError("Incompatible checkpoints. Please check model_index.json for the models.")142                print(config_dicts[0], config_dicts[1])143        print("Compatible model_index.json files found")144        # Step 2: Basic Validation has succeeded. Let's download the models and save them into our local files.145        cached_folders = []146        for pretrained_model_name_or_path, config_dict in zip(pretrained_model_name_or_path_list, config_dicts):147            folder_names = [k for k in config_dict.keys() if not k.startswith("_")]148            allow_patterns = [os.path.join(k, "*") for k in folder_names]149            allow_patterns += [150                WEIGHTS_NAME,151                SCHEDULER_CONFIG_NAME,152                CONFIG_NAME,153                ONNX_WEIGHTS_NAME,154                DiffusionPipeline.config_name,155            ]156            requested_pipeline_class = config_dict.get("_class_name")157            user_agent = {"diffusers": __version__, "pipeline_class": requested_pipeline_class}158 159            cached_folder = (160                pretrained_model_name_or_path161                if os.path.isdir(pretrained_model_name_or_path)162                else snapshot_download(163                    pretrained_model_name_or_path,164                    cache_dir=cache_dir,165                    resume_download=resume_download,166                    proxies=proxies,167                    local_files_only=local_files_only,168                    use_auth_token=use_auth_token,169                    revision=revision,170                    allow_patterns=allow_patterns,171                    user_agent=user_agent,172                )173            )174            print("Cached Folder", cached_folder)175            cached_folders.append(cached_folder)176 177        # Step 3:-178        # Load the first checkpoint as a diffusion pipeline and modify its module state_dict in place179        final_pipe = DiffusionPipeline.from_pretrained(180            cached_folders[0], torch_dtype=torch_dtype, device_map=device_map181        )182        final_pipe.to(self.device)183 184        checkpoint_path_2 = None185        if len(cached_folders) > 2:186            checkpoint_path_2 = os.path.join(cached_folders[2])187 188        if interp == "sigmoid":189            theta_func = CheckpointMergerPipeline.sigmoid190        elif interp == "inv_sigmoid":191            theta_func = CheckpointMergerPipeline.inv_sigmoid192        elif interp == "add_diff":193            theta_func = CheckpointMergerPipeline.add_difference194        else:195            theta_func = CheckpointMergerPipeline.weighted_sum196 197        # Find each module's state dict.198        for attr in final_pipe.config.keys():199            if not attr.startswith("_"):200                checkpoint_path_1 = os.path.join(cached_folders[1], attr)201                if os.path.exists(checkpoint_path_1):202                    files = [203                        *glob.glob(os.path.join(checkpoint_path_1, "*.safetensors")),204                        *glob.glob(os.path.join(checkpoint_path_1, "*.bin")),205                    ]206                    checkpoint_path_1 = files[0] if len(files) > 0 else None207                if len(cached_folders) < 3:208                    checkpoint_path_2 = None209                else:210                    checkpoint_path_2 = os.path.join(cached_folders[2], attr)211                    if os.path.exists(checkpoint_path_2):212                        files = [213                            *glob.glob(os.path.join(checkpoint_path_2, "*.safetensors")),214                            *glob.glob(os.path.join(checkpoint_path_2, "*.bin")),215                        ]216                        checkpoint_path_2 = files[0] if len(files) > 0 else None217                # For an attr if both checkpoint_path_1 and 2 are None, ignore.218                # If atleast one is present, deal with it according to interp method, of course only if the state_dict keys match.219                if checkpoint_path_1 is None and checkpoint_path_2 is None:220                    print(f"Skipping {attr}: not present in 2nd or 3d model")221                    continue222                try:223                    module = getattr(final_pipe, attr)224                    if isinstance(module, bool):  # ignore requires_safety_checker boolean225                        continue226                    theta_0 = getattr(module, "state_dict")227                    theta_0 = theta_0()228 229                    update_theta_0 = getattr(module, "load_state_dict")230                    theta_1 = (231                        safetensors.torch.load_file(checkpoint_path_1)232                        if (is_safetensors_available() and checkpoint_path_1.endswith(".safetensors"))233                        else torch.load(checkpoint_path_1, map_location="cpu")234                    )235                    theta_2 = None236                    if checkpoint_path_2:237                        theta_2 = (238                            safetensors.torch.load_file(checkpoint_path_2)239                            if (is_safetensors_available() and checkpoint_path_2.endswith(".safetensors"))240                            else torch.load(checkpoint_path_2, map_location="cpu")241                        )242 243                    if not theta_0.keys() == theta_1.keys():244                        print(f"Skipping {attr}: key mismatch")245                        continue246                    if theta_2 and not theta_1.keys() == theta_2.keys():247                        print(f"Skipping {attr}:y mismatch")248                except Exception as e:249                    print(f"Skipping {attr} do to an unexpected error: {str(e)}")250                    continue251                print(f"MERGING {attr}")252 253                for key in theta_0.keys():254                    if theta_2:255                        theta_0[key] = theta_func(theta_0[key], theta_1[key], theta_2[key], alpha)256                    else:257                        theta_0[key] = theta_func(theta_0[key], theta_1[key], None, alpha)258 259                del theta_1260                del theta_2261                update_theta_0(theta_0)262 263                del theta_0264        return final_pipe265 266    @staticmethod267    def weighted_sum(theta0, theta1, theta2, alpha):268        return ((1 - alpha) * theta0) + (alpha * theta1)269 270    # Smoothstep (https://en.wikipedia.org/wiki/Smoothstep)271    @staticmethod272    def sigmoid(theta0, theta1, theta2, alpha):273        alpha = alpha * alpha * (3 - (2 * alpha))274        return theta0 + ((theta1 - theta0) * alpha)275 276    # Inverse Smoothstep (https://en.wikipedia.org/wiki/Smoothstep)277    @staticmethod278    def inv_sigmoid(theta0, theta1, theta2, alpha):279        import math280 281        alpha = 0.5 - math.sin(math.asin(1.0 - 2.0 * alpha) / 3.0)282        return theta0 + ((theta1 - theta0) * alpha)283 284    @staticmethod285    def add_difference(theta0, theta1, theta2, alpha):286        return theta0 + (theta1 - theta2) * (1.0 - alpha)287