CoolFace
Apppublic

iyedjb/self-forcing

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
wan_wrapper.py312 linesDownload Raw Back to utils
1import types2from typing import List, Optional3import torch4from torch import nn5 6from utils.scheduler import SchedulerInterface, FlowMatchScheduler7from wan.modules.tokenizers import HuggingfaceTokenizer8from wan.modules.model import WanModel, RegisterTokens, GanAttentionBlock9from wan.modules.vae import _video_vae10from wan.modules.t5 import umt5_xxl11from wan.modules.causal_model import CausalWanModel12 13 14class WanTextEncoder(torch.nn.Module):15    def __init__(self) -> None:16        super().__init__()17 18        self.text_encoder = umt5_xxl(19            encoder_only=True,20            return_tokenizer=False,21            dtype=torch.float32,22            device=torch.device('cpu')23        ).eval().requires_grad_(False)24        self.text_encoder.load_state_dict(25            torch.load("wan_models/Wan2.1-T2V-1.3B/models_t5_umt5-xxl-enc-bf16.pth",26                       map_location='cpu', weights_only=False)27        )28 29        self.tokenizer = HuggingfaceTokenizer(30            name="wan_models/Wan2.1-T2V-1.3B/google/umt5-xxl/", seq_len=512, clean='whitespace')31 32    @property33    def device(self):34        # Assume we are always on GPU35        return torch.cuda.current_device()36 37    def forward(self, text_prompts: List[str]) -> dict:38        ids, mask = self.tokenizer(39            text_prompts, return_mask=True, add_special_tokens=True)40        ids = ids.to(self.device)41        mask = mask.to(self.device)42        seq_lens = mask.gt(0).sum(dim=1).long()43        context = self.text_encoder(ids, mask)44 45        for u, v in zip(context, seq_lens):46            u[v:] = 0.0  # set padding to 0.047 48        return {49            "prompt_embeds": context50        }51 52 53class WanVAEWrapper(torch.nn.Module):54    def __init__(self):55        super().__init__()56        mean = [57            -0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508,58            0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.292159        ]60        std = [61            2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743,62            3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.916063        ]64        self.mean = torch.tensor(mean, dtype=torch.float32)65        self.std = torch.tensor(std, dtype=torch.float32)66 67        # init model68        self.model = _video_vae(69            pretrained_path="wan_models/Wan2.1-T2V-1.3B/Wan2.1_VAE.pth",70            z_dim=16,71        ).eval().requires_grad_(False)72 73    def encode_to_latent(self, pixel: torch.Tensor) -> torch.Tensor:74        # pixel: [batch_size, num_channels, num_frames, height, width]75        device, dtype = pixel.device, pixel.dtype76        scale = [self.mean.to(device=device, dtype=dtype),77                 1.0 / self.std.to(device=device, dtype=dtype)]78 79        output = [80            self.model.encode(u.unsqueeze(0), scale).float().squeeze(0)81            for u in pixel82        ]83        output = torch.stack(output, dim=0)84        # from [batch_size, num_channels, num_frames, height, width]85        # to [batch_size, num_frames, num_channels, height, width]86        output = output.permute(0, 2, 1, 3, 4)87        return output88 89    def decode_to_pixel(self, latent: torch.Tensor, use_cache: bool = False) -> torch.Tensor:90        # from [batch_size, num_frames, num_channels, height, width]91        # to [batch_size, num_channels, num_frames, height, width]92        zs = latent.permute(0, 2, 1, 3, 4)93        if use_cache:94            assert latent.shape[0] == 1, "Batch size must be 1 when using cache"95 96        device, dtype = latent.device, latent.dtype97        scale = [self.mean.to(device=device, dtype=dtype),98                 1.0 / self.std.to(device=device, dtype=dtype)]99 100        if use_cache:101            decode_function = self.model.cached_decode102        else:103            decode_function = self.model.decode104 105        output = []106        for u in zs:107            output.append(decode_function(u.unsqueeze(0), scale).float().clamp_(-1, 1).squeeze(0))108        output = torch.stack(output, dim=0)109        # from [batch_size, num_channels, num_frames, height, width]110        # to [batch_size, num_frames, num_channels, height, width]111        output = output.permute(0, 2, 1, 3, 4)112        return output113 114 115class WanDiffusionWrapper(torch.nn.Module):116    def __init__(117            self,118            model_name="Wan2.1-T2V-1.3B",119            timestep_shift=8.0,120            is_causal=False,121            local_attn_size=-1,122            sink_size=0123    ):124        super().__init__()125 126        if is_causal:127            self.model = CausalWanModel.from_pretrained(128                f"wan_models/{model_name}/", local_attn_size=local_attn_size, sink_size=sink_size)129        else:130            self.model = WanModel.from_pretrained(f"wan_models/{model_name}/")131        self.model.eval()132 133        # For non-causal diffusion, all frames share the same timestep134        self.uniform_timestep = not is_causal135 136        self.scheduler = FlowMatchScheduler(137            shift=timestep_shift, sigma_min=0.0, extra_one_step=True138        )139        self.scheduler.set_timesteps(1000, training=True)140 141        self.seq_len = 32760  # [1, 21, 16, 60, 104]142        self.post_init()143 144    def enable_gradient_checkpointing(self) -> None:145        self.model.enable_gradient_checkpointing()146 147    def adding_cls_branch(self, atten_dim=1536, num_class=4, time_embed_dim=0) -> None:148        # NOTE: This is hard coded for WAN2.1-T2V-1.3B for now!!!!!!!!!!!!!!!!!!!!149        self._cls_pred_branch = nn.Sequential(150            # Input: [B, 384, 21, 60, 104]151            nn.LayerNorm(atten_dim * 3 + time_embed_dim),152            nn.Linear(atten_dim * 3 + time_embed_dim, 1536),153            nn.SiLU(),154            nn.Linear(atten_dim, num_class)155        )156        self._cls_pred_branch.requires_grad_(True)157        num_registers = 3158        self._register_tokens = RegisterTokens(num_registers=num_registers, dim=atten_dim)159        self._register_tokens.requires_grad_(True)160 161        gan_ca_blocks = []162        for _ in range(num_registers):163            block = GanAttentionBlock()164            gan_ca_blocks.append(block)165        self._gan_ca_blocks = nn.ModuleList(gan_ca_blocks)166        self._gan_ca_blocks.requires_grad_(True)167        # self.has_cls_branch = True168 169    def _convert_flow_pred_to_x0(self, flow_pred: torch.Tensor, xt: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:170        """171        Convert flow matching's prediction to x0 prediction.172        flow_pred: the prediction with shape [B, C, H, W]173        xt: the input noisy data with shape [B, C, H, W]174        timestep: the timestep with shape [B]175 176        pred = noise - x0177        x_t = (1-sigma_t) * x0 + sigma_t * noise178        we have x0 = x_t - sigma_t * pred179        see derivations https://chatgpt.com/share/67bf8589-3d04-8008-bc6e-4cf1a24e2d0e180        """181        # use higher precision for calculations182        original_dtype = flow_pred.dtype183        flow_pred, xt, sigmas, timesteps = map(184            lambda x: x.double().to(flow_pred.device), [flow_pred, xt,185                                                        self.scheduler.sigmas,186                                                        self.scheduler.timesteps]187        )188 189        timestep_id = torch.argmin(190            (timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)191        sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)192        x0_pred = xt - sigma_t * flow_pred193        return x0_pred.to(original_dtype)194 195    @staticmethod196    def _convert_x0_to_flow_pred(scheduler, x0_pred: torch.Tensor, xt: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:197        """198        Convert x0 prediction to flow matching's prediction.199        x0_pred: the x0 prediction with shape [B, C, H, W]200        xt: the input noisy data with shape [B, C, H, W]201        timestep: the timestep with shape [B]202 203        pred = (x_t - x_0) / sigma_t204        """205        # use higher precision for calculations206        original_dtype = x0_pred.dtype207        x0_pred, xt, sigmas, timesteps = map(208            lambda x: x.double().to(x0_pred.device), [x0_pred, xt,209                                                      scheduler.sigmas,210                                                      scheduler.timesteps]211        )212        timestep_id = torch.argmin(213            (timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)214        sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)215        flow_pred = (xt - x0_pred) / sigma_t216        return flow_pred.to(original_dtype)217 218    def forward(219        self,220        noisy_image_or_video: torch.Tensor, conditional_dict: dict,221        timestep: torch.Tensor, kv_cache: Optional[List[dict]] = None,222        crossattn_cache: Optional[List[dict]] = None,223        current_start: Optional[int] = None,224        classify_mode: Optional[bool] = False,225        concat_time_embeddings: Optional[bool] = False,226        clean_x: Optional[torch.Tensor] = None,227        aug_t: Optional[torch.Tensor] = None,228        cache_start: Optional[int] = None229    ) -> torch.Tensor:230        prompt_embeds = conditional_dict["prompt_embeds"]231 232        # [B, F] -> [B]233        if self.uniform_timestep:234            input_timestep = timestep[:, 0]235        else:236            input_timestep = timestep237 238        logits = None239        # X0 prediction240        if kv_cache is not None:241            flow_pred = self.model(242                noisy_image_or_video.permute(0, 2, 1, 3, 4),243                t=input_timestep, context=prompt_embeds,244                seq_len=self.seq_len,245                kv_cache=kv_cache,246                crossattn_cache=crossattn_cache,247                current_start=current_start,248                cache_start=cache_start249            ).permute(0, 2, 1, 3, 4)250        else:251            if clean_x is not None:252                # teacher forcing253                flow_pred = self.model(254                    noisy_image_or_video.permute(0, 2, 1, 3, 4),255                    t=input_timestep, context=prompt_embeds,256                    seq_len=self.seq_len,257                    clean_x=clean_x.permute(0, 2, 1, 3, 4),258                    aug_t=aug_t,259                ).permute(0, 2, 1, 3, 4)260            else:261                if classify_mode:262                    flow_pred, logits = self.model(263                        noisy_image_or_video.permute(0, 2, 1, 3, 4),264                        t=input_timestep, context=prompt_embeds,265                        seq_len=self.seq_len,266                        classify_mode=True,267                        register_tokens=self._register_tokens,268                        cls_pred_branch=self._cls_pred_branch,269                        gan_ca_blocks=self._gan_ca_blocks,270                        concat_time_embeddings=concat_time_embeddings271                    )272                    flow_pred = flow_pred.permute(0, 2, 1, 3, 4)273                else:274                    flow_pred = self.model(275                        noisy_image_or_video.permute(0, 2, 1, 3, 4),276                        t=input_timestep, context=prompt_embeds,277                        seq_len=self.seq_len278                    ).permute(0, 2, 1, 3, 4)279 280        pred_x0 = self._convert_flow_pred_to_x0(281            flow_pred=flow_pred.flatten(0, 1),282            xt=noisy_image_or_video.flatten(0, 1),283            timestep=timestep.flatten(0, 1)284        ).unflatten(0, flow_pred.shape[:2])285 286        if logits is not None:287            return flow_pred, pred_x0, logits288 289        return flow_pred, pred_x0290 291    def get_scheduler(self) -> SchedulerInterface:292        """293        Update the current scheduler with the interface's static method294        """295        scheduler = self.scheduler296        scheduler.convert_x0_to_noise = types.MethodType(297            SchedulerInterface.convert_x0_to_noise, scheduler)298        scheduler.convert_noise_to_x0 = types.MethodType(299            SchedulerInterface.convert_noise_to_x0, scheduler)300        scheduler.convert_velocity_to_x0 = types.MethodType(301            SchedulerInterface.convert_velocity_to_x0, scheduler)302        self.scheduler = scheduler303        return scheduler304 305    def post_init(self):306        """307        A few custom initialization steps that should be called after the object is created.308        Currently, the only one we have is to bind a few methods to scheduler.309        We can gradually add more methods here if needed.310        """311        self.get_scheduler()312