CoolFace
Apppublic

UAI-Software/PhotoMaker

sourceHugging Faceapache-2.0updated 3y agoView on Hugging Face
0likes
pipeline.py471 linesDownload Raw Back to root
1from typing import Any, Callable, Dict, List, Optional, Union, Tuple2from collections import OrderedDict3import os4import PIL5import numpy as np 6 7import torch8from torchvision import transforms as T9 10from safetensors import safe_open11from huggingface_hub.utils import validate_hf_hub_args12from transformers import CLIPImageProcessor, CLIPTokenizer13from diffusers import StableDiffusionXLPipeline14from diffusers.pipelines.stable_diffusion_xl import StableDiffusionXLPipelineOutput15from diffusers.utils import (16    _get_model_file,17    is_transformers_available,18    logging,19)20 21from model import PhotoMakerIDEncoder22 23PipelineImageInput = Union[24    PIL.Image.Image,25    torch.FloatTensor,26    List[PIL.Image.Image],27    List[torch.FloatTensor],28]29 30 31class PhotoMakerStableDiffusionXLPipeline(StableDiffusionXLPipeline):32    @validate_hf_hub_args33    def load_photomaker_adapter(34        self,35        pretrained_model_name_or_path_or_dict: Union[str, Dict[str, torch.Tensor]],36        weight_name: str,37        subfolder: str = '',38        trigger_word: str = 'img',39        **kwargs,40    ):41        """42        #TODO43        Parameters:44            pretrained_model_name_or_path_or_dict (`str` or `os.PathLike` or `dict`):45                Can be either:46 47                    - A string, the *model id* (for example `google/ddpm-celebahq-256`) of a pretrained model hosted on48                      the Hub.49                    - A path to a *directory* (for example `./my_model_directory`) containing the model weights saved50                      with [`ModelMixin.save_pretrained`].51                    - A [torch state52                      dict](https://pytorch.org/tutorials/beginner/saving_loading_models.html#what-is-a-state-dict).53 54            weight_name (`str`):55                The subfolder location of a model file within a larger model repository on the Hub or locally.56 57            subfolder (`str`, defaults to `""`):58                The subfolder location of a model file within a larger model repository on the Hub or locally.59 60            trigger_word (`str`, *optional*, defaults to `"img"`):61                The subfolder location of a model file within a larger model repository on the Hub or locally.            62        """63 64        # Load the main state dict first.65        cache_dir = kwargs.pop("cache_dir", None)66        force_download = kwargs.pop("force_download", False)67        resume_download = kwargs.pop("resume_download", False)68        proxies = kwargs.pop("proxies", None)69        local_files_only = kwargs.pop("local_files_only", None)70        token = kwargs.pop("token", None)71        revision = kwargs.pop("revision", None)72 73        user_agent = {74            "file_type": "attn_procs_weights",75            "framework": "pytorch",76        }77 78        if not isinstance(pretrained_model_name_or_path_or_dict, dict):79            model_file = _get_model_file(80                pretrained_model_name_or_path_or_dict,81                weights_name=weight_name,82                cache_dir=cache_dir,83                force_download=force_download,84                resume_download=resume_download,85                proxies=proxies,86                local_files_only=local_files_only,87                token=token,88                revision=revision,89                subfolder=subfolder,90                user_agent=user_agent,91            )92            if weight_name.endswith(".safetensors"):93                state_dict = {"id_encoder": {}, "lora_weights": {}}94                with safe_open(model_file, framework="pt", device="cpu") as f:95                    for key in f.keys():96                        if key.startswith("id_encoder."):97                            state_dict["id_encoder"][key.replace("id_encoder.", "")] = f.get_tensor(key)98                        elif key.startswith("lora_weights."):99                            state_dict["lora_weights"][key.replace("lora_weights.", "")] = f.get_tensor(key)100            else:101                state_dict = torch.load(model_file, map_location="cpu")102        else:103            state_dict = pretrained_model_name_or_path_or_dict104 105        keys = list(state_dict.keys())106        if keys != ["id_encoder", "lora_weights"]:107            raise ValueError("Required keys are (`id_encoder` and `lora_weights`) missing from the state dict.")108 109        self.trigger_word = trigger_word110        # load finetuned CLIP image encoder and fuse module here if it has not been registered to the pipeline yet111        print(f"Loading PhotoMaker components [1] id_encoder from [{pretrained_model_name_or_path_or_dict}]...")112        id_encoder = PhotoMakerIDEncoder()113        id_encoder.load_state_dict(state_dict["id_encoder"], strict=True)114        id_encoder = id_encoder.to(self.device, dtype=self.unet.dtype)    115        self.id_encoder = id_encoder116        self.id_image_processor = CLIPImageProcessor()117 118        # load lora into models119        print(f"Loading PhotoMaker components [2] lora_weights from [{pretrained_model_name_or_path_or_dict}]")120        self.load_lora_weights(state_dict["lora_weights"], adapter_name="photomaker")121 122        # Add trigger word token123        if self.tokenizer is not None: 124            self.tokenizer.add_tokens([self.trigger_word], special_tokens=True)125        126        self.tokenizer_2.add_tokens([self.trigger_word], special_tokens=True)127        128 129    def encode_prompt_with_trigger_word(130        self,131        prompt: str,132        prompt_2: Optional[str] = None,133        num_id_images: int = 1,134        device: Optional[torch.device] = None,135        prompt_embeds: Optional[torch.FloatTensor] = None,136        pooled_prompt_embeds: Optional[torch.FloatTensor] = None,137        class_tokens_mask: Optional[torch.LongTensor] = None,138    ):139        device = device or self._execution_device140 141        if prompt is not None and isinstance(prompt, str):142            batch_size = 1143        elif prompt is not None and isinstance(prompt, list):144            batch_size = len(prompt)145        else:146            batch_size = prompt_embeds.shape[0]147 148        # Find the token id of the trigger word149        image_token_id = self.tokenizer_2.convert_tokens_to_ids(self.trigger_word)150 151        # Define tokenizers and text encoders152        tokenizers = [self.tokenizer, self.tokenizer_2] if self.tokenizer is not None else [self.tokenizer_2]153        text_encoders = (154            [self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2]155        )156 157        if prompt_embeds is None:158            prompt_2 = prompt_2 or prompt159            prompt_embeds_list = []160            prompts = [prompt, prompt_2]161            for prompt, tokenizer, text_encoder in zip(prompts, tokenizers, text_encoders):162                input_ids = tokenizer.encode(prompt) # TODO: batch encode163                clean_index = 0164                clean_input_ids = []165                class_token_index = []166                # Find out the corrresponding class word token based on the newly added trigger word token167                for i, token_id in enumerate(input_ids):168                    if token_id == image_token_id:169                        class_token_index.append(clean_index - 1)170                    else:171                        clean_input_ids.append(token_id)172                        clean_index += 1173 174                if len(class_token_index) != 1:175                    raise ValueError(176                        f"PhotoMaker currently does not support multiple trigger words in a single prompt.\177                            Trigger word: {self.trigger_word}, Prompt: {prompt}."178                    )179                class_token_index = class_token_index[0]180 181                # Expand the class word token and corresponding mask182                class_token = clean_input_ids[class_token_index]183                clean_input_ids = clean_input_ids[:class_token_index] + [class_token] * num_id_images + \184                    clean_input_ids[class_token_index+1:]                185                    186                # Truncation or padding187                max_len = tokenizer.model_max_length188                if len(clean_input_ids) > max_len:189                    clean_input_ids = clean_input_ids[:max_len]190                else:191                    clean_input_ids = clean_input_ids + [tokenizer.pad_token_id] * (192                        max_len - len(clean_input_ids)193                    )194 195                class_tokens_mask = [True if class_token_index <= i < class_token_index+num_id_images else False \196                     for i in range(len(clean_input_ids))]197                198                clean_input_ids = torch.tensor(clean_input_ids, dtype=torch.long).unsqueeze(0)199                class_tokens_mask = torch.tensor(class_tokens_mask, dtype=torch.bool).unsqueeze(0)200                201                prompt_embeds = text_encoder(202                    clean_input_ids.to(device),203                    output_hidden_states=True,204                )205 206                # We are only ALWAYS interested in the pooled output of the final text encoder207                pooled_prompt_embeds = prompt_embeds[0]208                prompt_embeds = prompt_embeds.hidden_states[-2]209                prompt_embeds_list.append(prompt_embeds)210 211            prompt_embeds = torch.concat(prompt_embeds_list, dim=-1)212 213        prompt_embeds = prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device)214        class_tokens_mask = class_tokens_mask.to(device=device) # TODO: ignoring two-prompt case215 216        return prompt_embeds, pooled_prompt_embeds, class_tokens_mask217 218 219    @torch.no_grad()220    def __call__(221        self,222        prompt: Union[str, List[str]] = None,223        prompt_2: Optional[Union[str, List[str]]] = None,224        height: Optional[int] = None,225        width: Optional[int] = None,226        num_inference_steps: int = 50,227        denoising_end: Optional[float] = None,228        guidance_scale: float = 5.0,229        negative_prompt: Optional[Union[str, List[str]]] = None,230        negative_prompt_2: Optional[Union[str, List[str]]] = None,231        num_images_per_prompt: Optional[int] = 1,232        eta: float = 0.0,233        generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,234        latents: Optional[torch.FloatTensor] = None,235        prompt_embeds: Optional[torch.FloatTensor] = None,236        negative_prompt_embeds: Optional[torch.FloatTensor] = None,237        pooled_prompt_embeds: Optional[torch.FloatTensor] = None,238        negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None,239        output_type: Optional[str] = "pil",240        return_dict: bool = True,241        cross_attention_kwargs: Optional[Dict[str, Any]] = None,242        guidance_rescale: float = 0.0,243        original_size: Optional[Tuple[int, int]] = None,244        crops_coords_top_left: Tuple[int, int] = (0, 0),245        target_size: Optional[Tuple[int, int]] = None,246        callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,247        callback_steps: int = 1,248        # Added parameters (for PhotoMaker)249        input_id_images: PipelineImageInput = None,250        class_tokens_mask: Optional[torch.LongTensor] = None,251        prompt_embeds_text_only: Optional[torch.FloatTensor] = None,252        pooled_prompt_embeds_text_only: Optional[torch.FloatTensor] = None,253        start_merge_step: int = 0,254    ):255        # TODO: doc256        # 0. Default height and width to unet257        height = height or self.unet.config.sample_size * self.vae_scale_factor258        width = width or self.unet.config.sample_size * self.vae_scale_factor259 260        original_size = original_size or (height, width)261        target_size = target_size or (height, width)262 263        # 1. Check inputs. Raise error if not correct264        self.check_inputs(265            prompt,266            prompt_2,267            height,268            width,269            callback_steps,270            negative_prompt,271            negative_prompt_2,272            prompt_embeds,273            negative_prompt_embeds,274            pooled_prompt_embeds,275            negative_pooled_prompt_embeds,276        )277        #        278        if prompt_embeds is not None and class_tokens_mask is None:279            raise ValueError(280                "If `prompt_embeds` are provided, `class_tokens_mask` also have to be passed. Make sure to generate `class_tokens_mask` from the same tokenizer that was used to generate `prompt_embeds`."281            )282        # check the input id images283        if input_id_images is None:284            raise ValueError(285                "Provide `input_id_images`. Cannot leave `input_id_images` undefined for PhotoMaker pipeline."286            )287        if not isinstance(input_id_images, list):288            input_id_images = [input_id_images]289 290        # 2. Define call parameters291        if prompt is not None and isinstance(prompt, str):292            batch_size = 1293        elif prompt is not None and isinstance(prompt, list):294            batch_size = len(prompt)295        else:296            batch_size = prompt_embeds.shape[0]297 298        device = self._execution_device299 300        # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)301        # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`302        # corresponds to doing no classifier free guidance.303        do_classifier_free_guidance = guidance_scale > 1.0304 305        assert do_classifier_free_guidance306 307        # 3. Encode input prompt308        num_id_images = len(input_id_images)309        310        (311            prompt_embeds,312            pooled_prompt_embeds,313            class_tokens_mask,314        ) = self.encode_prompt_with_trigger_word(315            prompt=prompt,316            prompt_2=prompt_2,317            device=device,318            num_id_images=num_id_images,319            prompt_embeds=prompt_embeds,320            pooled_prompt_embeds=pooled_prompt_embeds,321            class_tokens_mask=class_tokens_mask,322        )323        324        # 4. Encode input prompt without the trigger word for delayed conditioning325        prompt_text_only = prompt.replace(" "+self.trigger_word, "") # sensitive to white space326        (327            prompt_embeds_text_only,328            negative_prompt_embeds,329            pooled_prompt_embeds_text_only, # TODO: replace the pooled_prompt_embeds with text only prompt330            negative_pooled_prompt_embeds,331        ) = self.encode_prompt(332            prompt=prompt_text_only,333            prompt_2=prompt_2,334            device=device,335            num_images_per_prompt=num_images_per_prompt,336            do_classifier_free_guidance=do_classifier_free_guidance,337            negative_prompt=negative_prompt,338            negative_prompt_2=negative_prompt_2,339            prompt_embeds=prompt_embeds_text_only,340            negative_prompt_embeds=negative_prompt_embeds,341            pooled_prompt_embeds=pooled_prompt_embeds_text_only,342            negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,343        )344 345        # 5. Prepare the input ID images346        dtype = next(self.id_encoder.parameters()).dtype347        if not isinstance(input_id_images[0], torch.Tensor):348            id_pixel_values = self.id_image_processor(input_id_images, return_tensors="pt").pixel_values349 350        id_pixel_values = id_pixel_values.unsqueeze(0).to(device=device, dtype=dtype) # TODO: multiple prompts351 352        # 6. Get the update text embedding with the stacked ID embedding353        prompt_embeds = self.id_encoder(id_pixel_values, prompt_embeds, class_tokens_mask)354        355        bs_embed, seq_len, _ = prompt_embeds.shape356        # duplicate text embeddings for each generation per prompt, using mps friendly method357        prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)358        prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)359        pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt).view(360            bs_embed * num_images_per_prompt, -1361        )362 363        # 7. Prepare timesteps364        self.scheduler.set_timesteps(num_inference_steps, device=device)365        timesteps = self.scheduler.timesteps366 367        # 8. Prepare latent variables368        num_channels_latents = self.unet.config.in_channels369        latents = self.prepare_latents(370            batch_size * num_images_per_prompt,371            num_channels_latents,372            height,373            width,374            prompt_embeds.dtype,375            device,376            generator,377            latents,378        )379 380        # 9. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline381        extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)382 383        # 10. Prepare added time ids & embeddings384        if self.text_encoder_2 is None:385            text_encoder_projection_dim = int(pooled_prompt_embeds.shape[-1])386        else:387            text_encoder_projection_dim = self.text_encoder_2.config.projection_dim388 389        add_time_ids = self._get_add_time_ids(390            original_size,391            crops_coords_top_left,392            target_size,393            dtype=prompt_embeds.dtype,394            text_encoder_projection_dim=text_encoder_projection_dim,395        )396        add_time_ids = torch.cat([add_time_ids, add_time_ids], dim=0)397        add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1)398 399        # 11. Denoising loop400        num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order401        with self.progress_bar(total=num_inference_steps) as progress_bar:402            for i, t in enumerate(timesteps):403                latent_model_input = (404                    torch.cat([latents] * 2) if do_classifier_free_guidance else latents405                )406                latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)407 408                if i <= start_merge_step:409                    current_prompt_embeds = torch.cat(410                        [negative_prompt_embeds, prompt_embeds_text_only], dim=0411                    )412                    add_text_embeds = torch.cat([negative_pooled_prompt_embeds, pooled_prompt_embeds_text_only], dim=0)413                else:414                    current_prompt_embeds = torch.cat(415                        [negative_prompt_embeds, prompt_embeds], dim=0416                    )417                    add_text_embeds = torch.cat([negative_pooled_prompt_embeds, pooled_prompt_embeds], dim=0)418                # predict the noise residual419                added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids}420                noise_pred = self.unet(421                    latent_model_input,422                    t,423                    encoder_hidden_states=current_prompt_embeds,424                    cross_attention_kwargs=cross_attention_kwargs,425                    added_cond_kwargs=added_cond_kwargs,426                    return_dict=False,427                )[0]428 429                # perform guidance430                if do_classifier_free_guidance:431                    noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)432                    noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)433 434                if do_classifier_free_guidance and guidance_rescale > 0.0:435                    # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf436                    noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale)437 438                # compute the previous noisy sample x_t -> x_t-1439                latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0]440 441                # call the callback, if provided442                if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):443                    progress_bar.update()444                    if callback is not None and i % callback_steps == 0:445                        callback(i, t, latents)446 447        # make sure the VAE is in float32 mode, as it overflows in float16448        if self.vae.dtype == torch.float16 and self.vae.config.force_upcast:449            self.upcast_vae()450            latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype)451 452        if not output_type == "latent":453            image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0]454        else:455            image = latents456            return StableDiffusionXLPipelineOutput(images=image)457 458        # apply watermark if available459        # if self.watermark is not None:460        #     image = self.watermark.apply_watermark(image)461 462        image = self.image_processor.postprocess(image, output_type=output_type)463 464        # Offload last model to CPU465        if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None:466            self.final_offload_hook.offload()467 468        if not return_dict:469            return (image,)470 471        return StableDiffusionXLPipelineOutput(images=image)