CoolFace
Apppublic

tsi-org/tango

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
clip_guided_stable_diffusion.py348 linesDownload Raw Back to community
1import inspect2from typing import List, Optional, Union3 4import torch5from torch import nn6from torch.nn import functional as F7from torchvision import transforms8from transformers import CLIPImageProcessor, CLIPModel, CLIPTextModel, CLIPTokenizer9 10from diffusers import (11    AutoencoderKL,12    DDIMScheduler,13    DiffusionPipeline,14    DPMSolverMultistepScheduler,15    LMSDiscreteScheduler,16    PNDMScheduler,17    UNet2DConditionModel,18)19from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion import StableDiffusionPipelineOutput20 21 22class MakeCutouts(nn.Module):23    def __init__(self, cut_size, cut_power=1.0):24        super().__init__()25 26        self.cut_size = cut_size27        self.cut_power = cut_power28 29    def forward(self, pixel_values, num_cutouts):30        sideY, sideX = pixel_values.shape[2:4]31        max_size = min(sideX, sideY)32        min_size = min(sideX, sideY, self.cut_size)33        cutouts = []34        for _ in range(num_cutouts):35            size = int(torch.rand([]) ** self.cut_power * (max_size - min_size) + min_size)36            offsetx = torch.randint(0, sideX - size + 1, ())37            offsety = torch.randint(0, sideY - size + 1, ())38            cutout = pixel_values[:, :, offsety : offsety + size, offsetx : offsetx + size]39            cutouts.append(F.adaptive_avg_pool2d(cutout, self.cut_size))40        return torch.cat(cutouts)41 42 43def spherical_dist_loss(x, y):44    x = F.normalize(x, dim=-1)45    y = F.normalize(y, dim=-1)46    return (x - y).norm(dim=-1).div(2).arcsin().pow(2).mul(2)47 48 49def set_requires_grad(model, value):50    for param in model.parameters():51        param.requires_grad = value52 53 54class CLIPGuidedStableDiffusion(DiffusionPipeline):55    """CLIP guided stable diffusion based on the amazing repo by @crowsonkb and @Jack00056    - https://github.com/Jack000/glid-3-xl57    - https://github.dev/crowsonkb/k-diffusion58    """59 60    def __init__(61        self,62        vae: AutoencoderKL,63        text_encoder: CLIPTextModel,64        clip_model: CLIPModel,65        tokenizer: CLIPTokenizer,66        unet: UNet2DConditionModel,67        scheduler: Union[PNDMScheduler, LMSDiscreteScheduler, DDIMScheduler, DPMSolverMultistepScheduler],68        feature_extractor: CLIPImageProcessor,69    ):70        super().__init__()71        self.register_modules(72            vae=vae,73            text_encoder=text_encoder,74            clip_model=clip_model,75            tokenizer=tokenizer,76            unet=unet,77            scheduler=scheduler,78            feature_extractor=feature_extractor,79        )80 81        self.normalize = transforms.Normalize(mean=feature_extractor.image_mean, std=feature_extractor.image_std)82        self.cut_out_size = (83            feature_extractor.size84            if isinstance(feature_extractor.size, int)85            else feature_extractor.size["shortest_edge"]86        )87        self.make_cutouts = MakeCutouts(self.cut_out_size)88 89        set_requires_grad(self.text_encoder, False)90        set_requires_grad(self.clip_model, False)91 92    def enable_attention_slicing(self, slice_size: Optional[Union[str, int]] = "auto"):93        if slice_size == "auto":94            # half the attention head size is usually a good trade-off between95            # speed and memory96            slice_size = self.unet.config.attention_head_dim // 297        self.unet.set_attention_slice(slice_size)98 99    def disable_attention_slicing(self):100        self.enable_attention_slicing(None)101 102    def freeze_vae(self):103        set_requires_grad(self.vae, False)104 105    def unfreeze_vae(self):106        set_requires_grad(self.vae, True)107 108    def freeze_unet(self):109        set_requires_grad(self.unet, False)110 111    def unfreeze_unet(self):112        set_requires_grad(self.unet, True)113 114    @torch.enable_grad()115    def cond_fn(116        self,117        latents,118        timestep,119        index,120        text_embeddings,121        noise_pred_original,122        text_embeddings_clip,123        clip_guidance_scale,124        num_cutouts,125        use_cutouts=True,126    ):127        latents = latents.detach().requires_grad_()128 129        latent_model_input = self.scheduler.scale_model_input(latents, timestep)130 131        # predict the noise residual132        noise_pred = self.unet(latent_model_input, timestep, encoder_hidden_states=text_embeddings).sample133 134        if isinstance(self.scheduler, (PNDMScheduler, DDIMScheduler, DPMSolverMultistepScheduler)):135            alpha_prod_t = self.scheduler.alphas_cumprod[timestep]136            beta_prod_t = 1 - alpha_prod_t137            # compute predicted original sample from predicted noise also called138            # "predicted x_0" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf139            pred_original_sample = (latents - beta_prod_t ** (0.5) * noise_pred) / alpha_prod_t ** (0.5)140 141            fac = torch.sqrt(beta_prod_t)142            sample = pred_original_sample * (fac) + latents * (1 - fac)143        elif isinstance(self.scheduler, LMSDiscreteScheduler):144            sigma = self.scheduler.sigmas[index]145            sample = latents - sigma * noise_pred146        else:147            raise ValueError(f"scheduler type {type(self.scheduler)} not supported")148 149        sample = 1 / self.vae.config.scaling_factor * sample150        image = self.vae.decode(sample).sample151        image = (image / 2 + 0.5).clamp(0, 1)152 153        if use_cutouts:154            image = self.make_cutouts(image, num_cutouts)155        else:156            image = transforms.Resize(self.cut_out_size)(image)157        image = self.normalize(image).to(latents.dtype)158 159        image_embeddings_clip = self.clip_model.get_image_features(image)160        image_embeddings_clip = image_embeddings_clip / image_embeddings_clip.norm(p=2, dim=-1, keepdim=True)161 162        if use_cutouts:163            dists = spherical_dist_loss(image_embeddings_clip, text_embeddings_clip)164            dists = dists.view([num_cutouts, sample.shape[0], -1])165            loss = dists.sum(2).mean(0).sum() * clip_guidance_scale166        else:167            loss = spherical_dist_loss(image_embeddings_clip, text_embeddings_clip).mean() * clip_guidance_scale168 169        grads = -torch.autograd.grad(loss, latents)[0]170 171        if isinstance(self.scheduler, LMSDiscreteScheduler):172            latents = latents.detach() + grads * (sigma**2)173            noise_pred = noise_pred_original174        else:175            noise_pred = noise_pred_original - torch.sqrt(beta_prod_t) * grads176        return noise_pred, latents177 178    @torch.no_grad()179    def __call__(180        self,181        prompt: Union[str, List[str]],182        height: Optional[int] = 512,183        width: Optional[int] = 512,184        num_inference_steps: Optional[int] = 50,185        guidance_scale: Optional[float] = 7.5,186        num_images_per_prompt: Optional[int] = 1,187        eta: float = 0.0,188        clip_guidance_scale: Optional[float] = 100,189        clip_prompt: Optional[Union[str, List[str]]] = None,190        num_cutouts: Optional[int] = 4,191        use_cutouts: Optional[bool] = True,192        generator: Optional[torch.Generator] = None,193        latents: Optional[torch.FloatTensor] = None,194        output_type: Optional[str] = "pil",195        return_dict: bool = True,196    ):197        if isinstance(prompt, str):198            batch_size = 1199        elif isinstance(prompt, list):200            batch_size = len(prompt)201        else:202            raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")203 204        if height % 8 != 0 or width % 8 != 0:205            raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")206 207        # get prompt text embeddings208        text_input = self.tokenizer(209            prompt,210            padding="max_length",211            max_length=self.tokenizer.model_max_length,212            truncation=True,213            return_tensors="pt",214        )215        text_embeddings = self.text_encoder(text_input.input_ids.to(self.device))[0]216        # duplicate text embeddings for each generation per prompt217        text_embeddings = text_embeddings.repeat_interleave(num_images_per_prompt, dim=0)218 219        if clip_guidance_scale > 0:220            if clip_prompt is not None:221                clip_text_input = self.tokenizer(222                    clip_prompt,223                    padding="max_length",224                    max_length=self.tokenizer.model_max_length,225                    truncation=True,226                    return_tensors="pt",227                ).input_ids.to(self.device)228            else:229                clip_text_input = text_input.input_ids.to(self.device)230            text_embeddings_clip = self.clip_model.get_text_features(clip_text_input)231            text_embeddings_clip = text_embeddings_clip / text_embeddings_clip.norm(p=2, dim=-1, keepdim=True)232            # duplicate text embeddings clip for each generation per prompt233            text_embeddings_clip = text_embeddings_clip.repeat_interleave(num_images_per_prompt, dim=0)234 235        # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)236        # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`237        # corresponds to doing no classifier free guidance.238        do_classifier_free_guidance = guidance_scale > 1.0239        # get unconditional embeddings for classifier free guidance240        if do_classifier_free_guidance:241            max_length = text_input.input_ids.shape[-1]242            uncond_input = self.tokenizer([""], padding="max_length", max_length=max_length, return_tensors="pt")243            uncond_embeddings = self.text_encoder(uncond_input.input_ids.to(self.device))[0]244            # duplicate unconditional embeddings for each generation per prompt245            uncond_embeddings = uncond_embeddings.repeat_interleave(num_images_per_prompt, dim=0)246 247            # For classifier free guidance, we need to do two forward passes.248            # Here we concatenate the unconditional and text embeddings into a single batch249            # to avoid doing two forward passes250            text_embeddings = torch.cat([uncond_embeddings, text_embeddings])251 252        # get the initial random noise unless the user supplied it253 254        # Unlike in other pipelines, latents need to be generated in the target device255        # for 1-to-1 results reproducibility with the CompVis implementation.256        # However this currently doesn't work in `mps`.257        latents_shape = (batch_size * num_images_per_prompt, self.unet.in_channels, height // 8, width // 8)258        latents_dtype = text_embeddings.dtype259        if latents is None:260            if self.device.type == "mps":261                # randn does not work reproducibly on mps262                latents = torch.randn(latents_shape, generator=generator, device="cpu", dtype=latents_dtype).to(263                    self.device264                )265            else:266                latents = torch.randn(latents_shape, generator=generator, device=self.device, dtype=latents_dtype)267        else:268            if latents.shape != latents_shape:269                raise ValueError(f"Unexpected latents shape, got {latents.shape}, expected {latents_shape}")270            latents = latents.to(self.device)271 272        # set timesteps273        accepts_offset = "offset" in set(inspect.signature(self.scheduler.set_timesteps).parameters.keys())274        extra_set_kwargs = {}275        if accepts_offset:276            extra_set_kwargs["offset"] = 1277 278        self.scheduler.set_timesteps(num_inference_steps, **extra_set_kwargs)279 280        # Some schedulers like PNDM have timesteps as arrays281        # It's more optimized to move all timesteps to correct device beforehand282        timesteps_tensor = self.scheduler.timesteps.to(self.device)283 284        # scale the initial noise by the standard deviation required by the scheduler285        latents = latents * self.scheduler.init_noise_sigma286 287        # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature288        # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.289        # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502290        # and should be between [0, 1]291        accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())292        extra_step_kwargs = {}293        if accepts_eta:294            extra_step_kwargs["eta"] = eta295 296        # check if the scheduler accepts generator297        accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())298        if accepts_generator:299            extra_step_kwargs["generator"] = generator300 301        for i, t in enumerate(self.progress_bar(timesteps_tensor)):302            # expand the latents if we are doing classifier free guidance303            latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents304            latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)305 306            # predict the noise residual307            noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample308 309            # perform classifier free guidance310            if do_classifier_free_guidance:311                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)312                noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)313 314            # perform clip guidance315            if clip_guidance_scale > 0:316                text_embeddings_for_guidance = (317                    text_embeddings.chunk(2)[1] if do_classifier_free_guidance else text_embeddings318                )319                noise_pred, latents = self.cond_fn(320                    latents,321                    t,322                    i,323                    text_embeddings_for_guidance,324                    noise_pred,325                    text_embeddings_clip,326                    clip_guidance_scale,327                    num_cutouts,328                    use_cutouts,329                )330 331            # compute the previous noisy sample x_t -> x_t-1332            latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample333 334        # scale and decode the image latents with vae335        latents = 1 / self.vae.config.scaling_factor * latents336        image = self.vae.decode(latents).sample337 338        image = (image / 2 + 0.5).clamp(0, 1)339        image = image.cpu().permute(0, 2, 3, 1).numpy()340 341        if output_type == "pil":342            image = self.numpy_to_pil(image)343 344        if not return_dict:345            return (image, None)346 347        return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=None)348