CoolFace
Apppublic

iyedjb/self-forcing

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
base.py223 linesDownload Raw Back to model
1from typing import Tuple2from einops import rearrange3from torch import nn4import torch.distributed as dist5import torch6 7from pipeline import SelfForcingTrainingPipeline8from utils.loss import get_denoising_loss9from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder, WanVAEWrapper10 11 12class BaseModel(nn.Module):13    def __init__(self, args, device):14        super().__init__()15        self._initialize_models(args, device)16 17        self.device = device18        self.args = args19        self.dtype = torch.bfloat16 if args.mixed_precision else torch.float3220        if hasattr(args, "denoising_step_list"):21            self.denoising_step_list = torch.tensor(args.denoising_step_list, dtype=torch.long)22            if args.warp_denoising_step:23                timesteps = torch.cat((self.scheduler.timesteps.cpu(), torch.tensor([0], dtype=torch.float32)))24                self.denoising_step_list = timesteps[1000 - self.denoising_step_list]25 26    def _initialize_models(self, args, device):27        self.real_model_name = getattr(args, "real_name", "Wan2.1-T2V-1.3B")28        self.fake_model_name = getattr(args, "fake_name", "Wan2.1-T2V-1.3B")29 30        self.generator = WanDiffusionWrapper(**getattr(args, "model_kwargs", {}), is_causal=True)31        self.generator.model.requires_grad_(True)32 33        self.real_score = WanDiffusionWrapper(model_name=self.real_model_name, is_causal=False)34        self.real_score.model.requires_grad_(False)35 36        self.fake_score = WanDiffusionWrapper(model_name=self.fake_model_name, is_causal=False)37        self.fake_score.model.requires_grad_(True)38 39        self.text_encoder = WanTextEncoder()40        self.text_encoder.requires_grad_(False)41 42        self.vae = WanVAEWrapper()43        self.vae.requires_grad_(False)44 45        self.scheduler = self.generator.get_scheduler()46        self.scheduler.timesteps = self.scheduler.timesteps.to(device)47 48    def _get_timestep(49            self,50            min_timestep: int,51            max_timestep: int,52            batch_size: int,53            num_frame: int,54            num_frame_per_block: int,55            uniform_timestep: bool = False56    ) -> torch.Tensor:57        """58        Randomly generate a timestep tensor based on the generator's task type. It uniformly samples a timestep59        from the range [min_timestep, max_timestep], and returns a tensor of shape [batch_size, num_frame].60        - If uniform_timestep, it will use the same timestep for all frames.61        - If not uniform_timestep, it will use a different timestep for each block.62        """63        if uniform_timestep:64            timestep = torch.randint(65                min_timestep,66                max_timestep,67                [batch_size, 1],68                device=self.device,69                dtype=torch.long70            ).repeat(1, num_frame)71            return timestep72        else:73            timestep = torch.randint(74                min_timestep,75                max_timestep,76                [batch_size, num_frame],77                device=self.device,78                dtype=torch.long79            )80            # make the noise level the same within every block81            if self.independent_first_frame:82                # the first frame is always kept the same83                timestep_from_second = timestep[:, 1:]84                timestep_from_second = timestep_from_second.reshape(85                    timestep_from_second.shape[0], -1, num_frame_per_block)86                timestep_from_second[:, :, 1:] = timestep_from_second[:, :, 0:1]87                timestep_from_second = timestep_from_second.reshape(88                    timestep_from_second.shape[0], -1)89                timestep = torch.cat([timestep[:, 0:1], timestep_from_second], dim=1)90            else:91                timestep = timestep.reshape(92                    timestep.shape[0], -1, num_frame_per_block)93                timestep[:, :, 1:] = timestep[:, :, 0:1]94                timestep = timestep.reshape(timestep.shape[0], -1)95            return timestep96 97 98class SelfForcingModel(BaseModel):99    def __init__(self, args, device):100        super().__init__(args, device)101        self.denoising_loss_func = get_denoising_loss(args.denoising_loss_type)()102 103    def _run_generator(104        self,105        image_or_video_shape,106        conditional_dict: dict,107        initial_latent: torch.tensor = None108    ) -> Tuple[torch.Tensor, torch.Tensor]:109        """110        Optionally simulate the generator's input from noise using backward simulation111        and then run the generator for one-step.112        Input:113            - image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].114            - conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).115            - unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).116            - clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.117            - initial_latent: a tensor containing the initial latents [B, F, C, H, W].118        Output:119            - pred_image: a tensor with shape [B, F, C, H, W].120            - denoised_timestep: an integer121        """122        # Step 1: Sample noise and backward simulate the generator's input123        assert getattr(self.args, "backward_simulation", True), "Backward simulation needs to be enabled"124        if initial_latent is not None:125            conditional_dict["initial_latent"] = initial_latent126        if self.args.i2v:127            noise_shape = [image_or_video_shape[0], image_or_video_shape[1] - 1, *image_or_video_shape[2:]]128        else:129            noise_shape = image_or_video_shape.copy()130 131        # During training, the number of generated frames should be uniformly sampled from132        # [21, self.num_training_frames], but still being a multiple of self.num_frame_per_block133        min_num_frames = 20 if self.args.independent_first_frame else 21134        max_num_frames = self.num_training_frames - 1 if self.args.independent_first_frame else self.num_training_frames135        assert max_num_frames % self.num_frame_per_block == 0136        assert min_num_frames % self.num_frame_per_block == 0137        max_num_blocks = max_num_frames // self.num_frame_per_block138        min_num_blocks = min_num_frames // self.num_frame_per_block139        num_generated_blocks = torch.randint(min_num_blocks, max_num_blocks + 1, (1,), device=self.device)140        dist.broadcast(num_generated_blocks, src=0)141        num_generated_blocks = num_generated_blocks.item()142        num_generated_frames = num_generated_blocks * self.num_frame_per_block143        if self.args.independent_first_frame and initial_latent is None:144            num_generated_frames += 1145            min_num_frames += 1146        # Sync num_generated_frames across all processes147        noise_shape[1] = num_generated_frames148 149        pred_image_or_video, denoised_timestep_from, denoised_timestep_to = self._consistency_backward_simulation(150            noise=torch.randn(noise_shape,151                              device=self.device, dtype=self.dtype),152            **conditional_dict,153        )154        # Slice last 21 frames155        if pred_image_or_video.shape[1] > 21:156            with torch.no_grad():157                # Reencode to get image latent158                latent_to_decode = pred_image_or_video[:, :-20, ...]159                # Deccode to video160                pixels = self.vae.decode_to_pixel(latent_to_decode)161                frame = pixels[:, -1:, ...].to(self.dtype)162                frame = rearrange(frame, "b t c h w -> b c t h w")163                # Encode frame to get image latent164                image_latent = self.vae.encode_to_latent(frame).to(self.dtype)165            pred_image_or_video_last_21 = torch.cat([image_latent, pred_image_or_video[:, -20:, ...]], dim=1)166        else:167            pred_image_or_video_last_21 = pred_image_or_video168 169        if num_generated_frames != min_num_frames:170            # Currently, we do not use gradient for the first chunk, since it contains image latents171            gradient_mask = torch.ones_like(pred_image_or_video_last_21, dtype=torch.bool)172            if self.args.independent_first_frame:173                gradient_mask[:, :1] = False174            else:175                gradient_mask[:, :self.num_frame_per_block] = False176        else:177            gradient_mask = None178 179        pred_image_or_video_last_21 = pred_image_or_video_last_21.to(self.dtype)180        return pred_image_or_video_last_21, gradient_mask, denoised_timestep_from, denoised_timestep_to181 182    def _consistency_backward_simulation(183        self,184        noise: torch.Tensor,185        **conditional_dict: dict186    ) -> torch.Tensor:187        """188        Simulate the generator's input from noise to avoid training/inference mismatch.189        See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.190        Here we use the consistency sampler (https://arxiv.org/abs/2303.01469)191        Input:192            - noise: a tensor sampled from N(0, 1) with shape [B, F, C, H, W] where the number of frame is 1 for images.193            - conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).194        Output:195            - output: a tensor with shape [B, T, F, C, H, W].196            T is the total number of timesteps. output[0] is a pure noise and output[i] and i>0197            represents the x0 prediction at each timestep.198        """199        if self.inference_pipeline is None:200            self._initialize_inference_pipeline()201 202        return self.inference_pipeline.inference_with_trajectory(203            noise=noise, **conditional_dict204        )205 206    def _initialize_inference_pipeline(self):207        """208        Lazy initialize the inference pipeline during the first backward simulation run.209        Here we encapsulate the inference code with a model-dependent outside function.210        We pass our FSDP-wrapped modules into the pipeline to save memory.211        """212        self.inference_pipeline = SelfForcingTrainingPipeline(213            denoising_step_list=self.denoising_step_list,214            scheduler=self.scheduler,215            generator=self.generator,216            num_frame_per_block=self.num_frame_per_block,217            independent_first_frame=self.args.independent_first_frame,218            same_step_across_blocks=self.args.same_step_across_blocks,219            last_step_only=self.args.last_step_only,220            num_max_frames=self.num_training_frames,221            context_noise=self.args.context_noise222        )223