CoolFace
Apppublic

LTT/PRM

sourceHugging Faceupdated 1y agoView on Hugging Face
24likes
model.py273 linesDownload Raw Back to zero123plus
1import os2import numpy as np3import torch4import torch.nn as nn5import torch.nn.functional as F6import pytorch_lightning as pl7from tqdm import tqdm8from torchvision.transforms import v29from torchvision.utils import make_grid, save_image10from einops import rearrange11 12from src.utils.train_util import instantiate_from_config13from diffusers import DiffusionPipeline, EulerAncestralDiscreteScheduler, DDPMScheduler, UNet2DConditionModel14from .pipeline import RefOnlyNoisedUNet15 16 17def scale_latents(latents):18    latents = (latents - 0.22) * 0.7519    return latents20 21 22def unscale_latents(latents):23    latents = latents / 0.75 + 0.2224    return latents25 26 27def scale_image(image):28    image = image * 0.5 / 0.829    return image30 31 32def unscale_image(image):33    image = image / 0.5 * 0.834    return image35 36 37def extract_into_tensor(a, t, x_shape):38    b, *_ = t.shape39    out = a.gather(-1, t)40    return out.reshape(b, *((1,) * (len(x_shape) - 1)))41 42 43class MVDiffusion(pl.LightningModule):44    def __init__(45        self,46        stable_diffusion_config,47        drop_cond_prob=0.1,48    ):49        super(MVDiffusion, self).__init__()50 51        self.drop_cond_prob = drop_cond_prob52 53        self.register_schedule()54 55        # init modules56        pipeline = DiffusionPipeline.from_pretrained(**stable_diffusion_config)57        pipeline.scheduler = EulerAncestralDiscreteScheduler.from_config(58            pipeline.scheduler.config, timestep_spacing='trailing'59        )60        self.pipeline = pipeline61 62        train_sched = DDPMScheduler.from_config(self.pipeline.scheduler.config)63        if isinstance(self.pipeline.unet, UNet2DConditionModel):64            self.pipeline.unet = RefOnlyNoisedUNet(self.pipeline.unet, train_sched, self.pipeline.scheduler)65 66        self.train_scheduler = train_sched      # use ddpm scheduler during training67 68        self.unet = pipeline.unet69 70        # validation output buffer71        self.validation_step_outputs = []72 73    def register_schedule(self):74        self.num_timesteps = 100075 76        # replace scaled_linear schedule with linear schedule as Zero123++77        beta_start = 0.0008578        beta_end = 0.012079        betas = torch.linspace(beta_start, beta_end, 1000, dtype=torch.float32)80        81        alphas = 1. - betas82        alphas_cumprod = torch.cumprod(alphas, dim=0)83        alphas_cumprod_prev = torch.cat([torch.ones(1, dtype=torch.float64), alphas_cumprod[:-1]], 0)84 85        self.register_buffer('betas', betas.float())86        self.register_buffer('alphas_cumprod', alphas_cumprod.float())87        self.register_buffer('alphas_cumprod_prev', alphas_cumprod_prev.float())88 89        # calculations for diffusion q(x_t | x_{t-1}) and others90        self.register_buffer('sqrt_alphas_cumprod', torch.sqrt(alphas_cumprod).float())91        self.register_buffer('sqrt_one_minus_alphas_cumprod', torch.sqrt(1 - alphas_cumprod).float())92        93        self.register_buffer('sqrt_recip_alphas_cumprod', torch.sqrt(1. / alphas_cumprod).float())94        self.register_buffer('sqrt_recipm1_alphas_cumprod', torch.sqrt(1. / alphas_cumprod - 1).float())95    96    def on_fit_start(self):97        device = torch.device(f'cuda:{self.global_rank}')98        self.pipeline.to(device)99        if self.global_rank == 0:100            os.makedirs(os.path.join(self.logdir, 'images'), exist_ok=True)101            os.makedirs(os.path.join(self.logdir, 'images_val'), exist_ok=True)102    103    def prepare_batch_data(self, batch):104        # prepare stable diffusion input105        cond_imgs = batch['cond_imgs']      # (B, C, H, W)106        cond_imgs = cond_imgs.to(self.device)107 108        # random resize the condition image109        cond_size = np.random.randint(128, 513)110        cond_imgs = v2.functional.resize(cond_imgs, cond_size, interpolation=3, antialias=True).clamp(0, 1)111 112        target_imgs = batch['target_imgs']  # (B, 6, C, H, W)113        target_imgs = v2.functional.resize(target_imgs, 320, interpolation=3, antialias=True).clamp(0, 1)114        target_imgs = rearrange(target_imgs, 'b (x y) c h w -> b c (x h) (y w)', x=3, y=2)    # (B, C, 3H, 2W)115        target_imgs = target_imgs.to(self.device)116 117        return cond_imgs, target_imgs118    119    @torch.no_grad()120    def forward_vision_encoder(self, images):121        dtype = next(self.pipeline.vision_encoder.parameters()).dtype122        image_pil = [v2.functional.to_pil_image(images[i]) for i in range(images.shape[0])]123        image_pt = self.pipeline.feature_extractor_clip(images=image_pil, return_tensors="pt").pixel_values124        image_pt = image_pt.to(device=self.device, dtype=dtype)125        global_embeds = self.pipeline.vision_encoder(image_pt, output_hidden_states=False).image_embeds126        global_embeds = global_embeds.unsqueeze(-2)127 128        encoder_hidden_states = self.pipeline._encode_prompt("", self.device, 1, False)[0]129        ramp = global_embeds.new_tensor(self.pipeline.config.ramping_coefficients).unsqueeze(-1)130        encoder_hidden_states = encoder_hidden_states + global_embeds * ramp131 132        return encoder_hidden_states133    134    @torch.no_grad()135    def encode_condition_image(self, images):136        dtype = next(self.pipeline.vae.parameters()).dtype137        image_pil = [v2.functional.to_pil_image(images[i]) for i in range(images.shape[0])]138        image_pt = self.pipeline.feature_extractor_vae(images=image_pil, return_tensors="pt").pixel_values139        image_pt = image_pt.to(device=self.device, dtype=dtype)140        latents = self.pipeline.vae.encode(image_pt).latent_dist.sample()141        return latents142    143    @torch.no_grad()144    def encode_target_images(self, images):145        dtype = next(self.pipeline.vae.parameters()).dtype146        # equals to scaling images to [-1, 1] first and then call scale_image147        images = (images - 0.5) / 0.8   # [-0.625, 0.625]148        posterior = self.pipeline.vae.encode(images.to(dtype)).latent_dist149        latents = posterior.sample() * self.pipeline.vae.config.scaling_factor150        latents = scale_latents(latents)151        return latents152    153    def forward_unet(self, latents, t, prompt_embeds, cond_latents):154        dtype = next(self.pipeline.unet.parameters()).dtype155        latents = latents.to(dtype)156        prompt_embeds = prompt_embeds.to(dtype)157        cond_latents = cond_latents.to(dtype)158        cross_attention_kwargs = dict(cond_lat=cond_latents)159        pred_noise = self.pipeline.unet(160            latents,161            t,162            encoder_hidden_states=prompt_embeds,163            cross_attention_kwargs=cross_attention_kwargs,164            return_dict=False,165        )[0]166        return pred_noise167    168    def predict_start_from_z_and_v(self, x_t, t, v):169        return (170            extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * x_t -171            extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * v172        )173 174    def get_v(self, x, noise, t):175        return (176            extract_into_tensor(self.sqrt_alphas_cumprod, t, x.shape) * noise -177            extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x.shape) * x178        )179    180    def training_step(self, batch, batch_idx):181        # get input182        cond_imgs, target_imgs = self.prepare_batch_data(batch)183 184        # sample random timestep185        B = cond_imgs.shape[0]186        187        t = torch.randint(0, self.num_timesteps, size=(B,)).long().to(self.device)188 189        # classifier-free guidance190        if np.random.rand() < self.drop_cond_prob:191            prompt_embeds = self.pipeline._encode_prompt([""]*B, self.device, 1, False)192            cond_latents = self.encode_condition_image(torch.zeros_like(cond_imgs))193        else:194            prompt_embeds = self.forward_vision_encoder(cond_imgs)195            cond_latents = self.encode_condition_image(cond_imgs)196 197        latents = self.encode_target_images(target_imgs)198        noise = torch.randn_like(latents)199        latents_noisy = self.train_scheduler.add_noise(latents, noise, t)200        201        v_pred = self.forward_unet(latents_noisy, t, prompt_embeds, cond_latents)202        v_target = self.get_v(latents, noise, t)203 204        loss, loss_dict = self.compute_loss(v_pred, v_target)205 206        # logging207        self.log_dict(loss_dict, prog_bar=True, logger=True, on_step=True, on_epoch=True)208        self.log("global_step", self.global_step, prog_bar=True, logger=True, on_step=True, on_epoch=False)209        lr = self.optimizers().param_groups[0]['lr']210        self.log('lr_abs', lr, prog_bar=True, logger=True, on_step=True, on_epoch=False)211 212        if self.global_step % 500 == 0 and self.global_rank == 0:213            with torch.no_grad():214                latents_pred = self.predict_start_from_z_and_v(latents_noisy, t, v_pred)215 216                latents = unscale_latents(latents_pred)217                images = unscale_image(self.pipeline.vae.decode(latents / self.pipeline.vae.config.scaling_factor, return_dict=False)[0])   # [-1, 1]218                images = (images * 0.5 + 0.5).clamp(0, 1)219                images = torch.cat([target_imgs, images], dim=-2)220 221                grid = make_grid(images, nrow=images.shape[0], normalize=True, value_range=(0, 1))222                save_image(grid, os.path.join(self.logdir, 'images', f'train_{self.global_step:07d}.png'))223 224        return loss225        226    def compute_loss(self, noise_pred, noise_gt):227        loss = F.mse_loss(noise_pred, noise_gt)228 229        prefix = 'train'230        loss_dict = {}231        loss_dict.update({f'{prefix}/loss': loss})232 233        return loss, loss_dict234 235    @torch.no_grad()236    def validation_step(self, batch, batch_idx):237        # get input238        cond_imgs, target_imgs = self.prepare_batch_data(batch)239 240        images_pil = [v2.functional.to_pil_image(cond_imgs[i]) for i in range(cond_imgs.shape[0])]241 242        outputs = []243        for cond_img in images_pil:244            latent = self.pipeline(cond_img, num_inference_steps=75, output_type='latent').images245            image = unscale_image(self.pipeline.vae.decode(latent / self.pipeline.vae.config.scaling_factor, return_dict=False)[0])   # [-1, 1]246            image = (image * 0.5 + 0.5).clamp(0, 1)247            outputs.append(image)248        outputs = torch.cat(outputs, dim=0).to(self.device)249        images = torch.cat([target_imgs, outputs], dim=-2)250        251        self.validation_step_outputs.append(images)252    253    @torch.no_grad()254    def on_validation_epoch_end(self):255        images = torch.cat(self.validation_step_outputs, dim=0)256 257        all_images = self.all_gather(images)258        all_images = rearrange(all_images, 'r b c h w -> (r b) c h w')259 260        if self.global_rank == 0:261            grid = make_grid(all_images, nrow=8, normalize=True, value_range=(0, 1))262            save_image(grid, os.path.join(self.logdir, 'images_val', f'val_{self.global_step:07d}.png'))263 264        self.validation_step_outputs.clear()  # free memory265 266    def configure_optimizers(self):267        lr = self.learning_rate268 269        optimizer = torch.optim.AdamW(self.unet.parameters(), lr=lr)270        scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, 3000, eta_min=lr/4)271 272        return {'optimizer': optimizer, 'lr_scheduler': scheduler}273