CoolFace
Apppublic

prernaaa12/abot-world-interactive

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
wan_wrapper.py872 linesDownload Raw Back to utils
1import types2from pathlib import Path3from typing import List, Optional4import torch5from torch import nn6from safetensors.torch import load_file as load_safetensors_file7 8from utils.scheduler import SchedulerInterface, FlowMatchScheduler9import os10 11 12def _wan_models_path(*parts) -> str:13    """Resolve wan_models path relative to project root (works with symlink and any cwd)."""14    root = Path(__file__).resolve().parent.parent15    return str((root / "wan_models").joinpath(Path(*parts)))16 17 18def _resolve_wan_path(path: str) -> str:19    """If path starts with wan_models/, resolve to absolute path (project root); else return as-is."""20    if path and path.startswith("wan_models/"):21        return _wan_models_path(path[len("wan_models/"):])22    return path23 24 25def _resolve_wan_path_with_dir(path: str, wan_models_dir: Optional[str] = None) -> str:26    """Resolve path: if wan_models_dir is set and path starts with wan_models/, use wan_models_dir as base; else _resolve_wan_path."""27    if not path:28        return path29    if wan_models_dir and path.startswith("wan_models/"):30        return os.path.join(wan_models_dir, path[len("wan_models/"):])31    return _resolve_wan_path(path)32 33 34def model_kwargs_with_relative_rope(args, default: bool = False) -> dict:35    """Merge top-level use_relative_rope into model_kwargs with a stable default."""36    raw_model_kwargs = getattr(args, "model_kwargs", {}) or {}37    model_kwargs = dict(raw_model_kwargs)38    if "use_relative_rope" not in model_kwargs:39        try:40            model_kwargs["use_relative_rope"] = bool(getattr(args, "use_relative_rope"))41        except Exception:42            model_kwargs["use_relative_rope"] = bool(default)43    return model_kwargs44 45from wan.modules.tokenizers import HuggingfaceTokenizer46from wan.modules.model import WanModel47from wan.modules.t5 import umt5_xxl48from wan.modules.causal_model import CausalWanModel49 50class WanTextEncoder(torch.nn.Module):51    def __init__(52        self,53        tokenizer_path="wan_models/Wan2.1-T2V-1.3B/google/umt5-xxl/",54        encoder_pth_path="wan_models/Wan2.1-T2V-1.3B/models_t5_umt5-xxl-enc-bf16.pth",55    ) -> None:56        super().__init__()57        # tokenizer_path = _resolve_wan_path_with_dir(tokenizer_path, wan_models_dir)58        # encoder_pth_path = _resolve_wan_path_with_dir(encoder_pth_path, wan_models_dir)59 60        self.text_encoder = umt5_xxl(61            encoder_only=True,62            return_tokenizer=False,63            dtype=torch.bfloat16,64            device=torch.device('cpu')65        ).eval().requires_grad_(False)66        state_dict = torch.load(encoder_pth_path,67                                map_location='cpu', weights_only=False)68        self.text_encoder.load_state_dict(state_dict)69        del state_dict70 71        self.tokenizer = HuggingfaceTokenizer(72            name=tokenizer_path, seq_len=512, clean='whitespace')73 74    @property75    def device(self):76        return next(self.text_encoder.parameters()).device77 78    def forward(self, text_prompts: List[str], device: torch.device = None) -> dict:79        ids, mask = self.tokenizer(80            text_prompts, return_mask=True, add_special_tokens=True)81        # When DynamicSwapInstaller is active, self.device returns cpu because82        # parameters are swapped to GPU only during forward.  Use the explicitly83        # passed device (the intended execution device) when available.84        target_device = device if device is not None else self.device85        ids = ids.to(target_device)86        mask = mask.to(target_device)87        seq_lens = mask.gt(0).sum(dim=1).long()88        context = self.text_encoder(ids, mask)89 90        for u, v in zip(context, seq_lens):91            u[v:] = 0.0  # set padding to 0.092 93        return {94            "prompt_embeds": context95        }96 97 98class WanVAEWrapper(torch.nn.Module):99    def __init__(100        self,101        pretrained_path=None,102        z_dim=48,103        vae_type="Wan2.2_VAE",104        wan_models_dir=None,105    ):106        super().__init__()107        if vae_type != "Wan2.2_VAE":108            raise ValueError(f"Unsupported vae_type={vae_type!r}; only 'Wan2.2_VAE' is supported.")109        from wan.modules.vae2_2 import _video_vae110        self.mean = torch.tensor([111                -0.2289, -0.0052, -0.1323, -0.2339, -0.2799,  0.0174,  0.1838,  0.1557,112                -0.1382,  0.0542,  0.2813,  0.0891,  0.1570, -0.0098,  0.0375, -0.1825,113                -0.2246, -0.1207, -0.0698,  0.5109,  0.2665, -0.2108, -0.2158,  0.2502,114                -0.2055, -0.0322,  0.1109,  0.1567, -0.0729,  0.0899, -0.2799, -0.1230,115                -0.0313, -0.1649,  0.0117,  0.0723, -0.2839, -0.2083, -0.0520,  0.3748,116                0.0152,  0.1957,  0.1433, -0.2944,  0.3573, -0.0548, -0.1681, -0.0667,117        ], dtype=torch.float32)118 119        self.std = torch.tensor([120                0.4765, 1.0364, 0.4514, 1.1677, 0.5313, 0.4990, 0.4818, 0.5013,121                0.8158, 1.0344, 0.5894, 1.0901, 0.6885, 0.6165, 0.8454, 0.4978,122                0.5759, 0.3523, 0.7135, 0.6804, 0.5833, 1.4146, 0.8986, 0.5659,123                0.7069, 0.5338, 0.4889, 0.4917, 0.4069, 0.4999, 0.6866, 0.4093,124                0.5709, 0.6065, 0.6415, 0.4944, 0.5726, 1.2042, 0.5458, 1.6887,125                0.3971, 1.0600, 0.3943, 0.5537, 0.5444, 0.4089, 0.7468, 0.7744,126        ], dtype=torch.float32)127        self.scale = [self.mean, 1.0 / self.std]128        self.upsampling_factor = 16129 130        z_dim = 48131        self.z_dim = z_dim132        self.model = _video_vae(pretrained_path=pretrained_path,133            z_dim=z_dim,).eval().requires_grad_(False)134 135    def generate_noise(self, shape, seed=None, rand_device="cpu", rand_torch_dtype=torch.float32, device=None, torch_dtype=None):136        # Initialize Gaussian noise137        generator = None if seed is None else torch.Generator(rand_device).manual_seed(seed)138        noise = torch.randn(shape, generator=generator, device=rand_device, dtype=rand_torch_dtype)139        noise = noise.to(dtype=torch_dtype, device=device)140        return noise141    142    def encode_to_latent(self, pixel: torch.Tensor) -> torch.Tensor:143        # pixel: [batch_size, num_channels, num_frames, height, width]144        device, dtype = pixel.device, pixel.dtype145        scale = [self.mean.to(device=device, dtype=dtype),146                 1.0 / self.std.to(device=device, dtype=dtype)]147 148        output = [149            self.model.encode(u.unsqueeze(0), scale).float().squeeze(0)150            for u in pixel151        ]152        output = torch.stack(output, dim=0)153        # from [batch_size, num_channels, num_frames, height, width]154        # to [batch_size, num_frames, num_channels, height, width]155        output = output.permute(0, 2, 1, 3, 4)156        return output157 158    def decode_to_pixel(self, latent: torch.Tensor, use_cache: bool = False, return_in_cpu: bool = False) -> torch.Tensor:159        # from [batch_size, num_frames, num_channels, height, width]160        # to [batch_size, num_channels, num_frames, height, width]161        zs = latent.permute(0, 2, 1, 3, 4)162        if use_cache:163            assert latent.shape[0] == 1, "Batch size must be 1 when using cache"164 165        device, dtype = latent.device, latent.dtype166        scale = [self.mean.to(device=device, dtype=dtype),167                 1.0 / self.std.to(device=device, dtype=dtype)]168 169        if use_cache:170            decode_function = self.model.cached_decode171        else:172            decode_function = self.model.decode173 174        output = []175        for u in zs:176            decoded = decode_function(u.unsqueeze(0), scale).float().clamp_(-1, 1).squeeze(0)177            if return_in_cpu:178                decoded = decoded.cpu()179            output.append(decoded)180        output = torch.stack(output, dim=0)181        # from [batch_size, num_channels, num_frames, height, width]182        # to [batch_size, num_frames, num_channels, height, width]183        output = output.permute(0, 2, 1, 3, 4)184        return output185 186 187class TAEW2_2VAEWrapper(torch.nn.Module):188    """189    VAE wrapper using TAEHV (TAEW2.2) for faster decoding.190    Requires: pip install taehv (or install from https://github.com/madebyollin/taehv)191    Checkpoint: taew2_2.pth (download from taehv releases)192    """193    def __init__(self, checkpoint_path: str = "taew2_2.pth", dtype=torch.float16):194        super().__init__()195        try:196            from wan.modules.taehv import TAEHV, StreamingTAEHV197        except ImportError as e:198            raise ImportError(199                "taehv is required for TAEW2.2 VAE. Install with: pip install taehv"200            ) from e201        self.taehv = TAEHV(checkpoint_path).to(dtype).eval().requires_grad_(False)202        self.taehv = StreamingTAEHV(self.taehv)203        self.dtype = dtype204        # For compatibility with pipeline.vae.model.clear_cache()205        self.model = _TAEW2_2ModelRef(self)206 207    def warmup_first_frame(self, first_frame_latent: torch.Tensor):208        """Warm up the streaming decoder's MemBlock memory with the first-frame latent.209 210        The TAeW2.2 MemBlocks use zero-initialized past context for the first frame,211        causing blur.  By feeding the first-frame latent as a warmup pass (output212        discarded), subsequent decodes benefit from real temporal context.213 214        Args:215            first_frame_latent: [B, 1, C, H, W] latent of the first frame.216        """217        if first_frame_latent is None:218            return219        # Reset decoder state, then feed first frame as warmup220        self.taehv.reset()221        with torch.no_grad(), torch.autocast(device_type="cuda", dtype=self.dtype):222            # Feed the first-frame latent to populate MemBlock memory;223            # the output (startup frames) is discarded.224            _ = self.taehv.decode(first_frame_latent)225 226    def decode_to_pixel(227        self,228        latent: torch.Tensor,229        use_cache: bool = False,230        return_in_cpu: bool = False231    ) -> torch.Tensor:232        # latent: [B, F, C, H, W] = [B, T, C, H, W] (same as TAEHV's NTCHW)233        # use_cache=True -> parallel=False for lower memory (streaming)234        parallel = not use_cache235        with torch.autocast(device_type="cuda", dtype=self.dtype):236            # out = self.taehv.decode_video(237            #     latent, parallel=parallel, show_progress_bar=False238            # )239            out = self.taehv.decode(latent)240        # TAEHV returns [0, 1], convert to [-1, 1] to match WanVAEWrapper241        out = out.mul(2).sub(1).clamp(-1, 1).float()242        if return_in_cpu:243            out = out.cpu()244        return out245 246 247class _TAEW2_2ModelRef:248    """Dummy ref for clear_cache compatibility; delegates to StreamingTAEHV.reset()."""249 250    def __init__(self, parent):251        self._parent = parent252 253    def clear_cache(self):254        self._parent.taehv.reset()255 256 257class MGLightVAEWrapper(torch.nn.Module):258    """VAE wrapper using MG-LightVAE (pruned Wan2.2 VAE) for faster decoding.259 260    Wraps the ``Wan2_2_VAE`` class which supports different pruning rates.261    The encoder uses the full (unpruned) Wan2.2 VAE teacher, while the decoder262    uses the pruned student model.263 264    Args:265        vae_pth: Path to the pruned LightVAE checkpoint (student decoder).266        lightvae_pruning_rate: Pruning rate for the decoder (e.g. 0.5, 0.75).267        lightvae_encoder_vae_pth: Path to the full Wan2.2 VAE checkpoint268            (teacher encoder). Required for mg_lightvae.269        dtype: Data type for the VAE model.270        device: Device to load the VAE on.271    """272 273    def __init__(274        self,275        vae_pth: str,276        lightvae_pruning_rate: float = 0.75,277        lightvae_encoder_vae_pth: str | None = None,278        dtype=torch.float,279        device="cpu",280    ):281        super().__init__()282        from wan.modules.vae2_2 import Wan2_2_VAE283 284        self._vae = Wan2_2_VAE(285            z_dim=48,286            c_dim=160,287            vae_pth=vae_pth,288            dtype=dtype,289            device=device,290            vae_type="mg_lightvae",291            lightvae_pruning_rate=lightvae_pruning_rate,292            lightvae_encoder_vae_pth=lightvae_encoder_vae_pth,293        )294        # Register model (pruned decoder) and encoder_model (teacher encoder)295        # as submodules so that .to(), .eval(), .requires_grad_() propagate.296        self.model = self._vae.model297        if self._vae.encoder_model is not None:298            self.encoder_model = self._vae.encoder_model299        else:300            self.encoder_model = None301 302        self.z_dim = 48303        self.upsampling_factor = 16304        self.mean = self._vae.scale[0]305        self.std = 1.0 / self._vae.scale[1]306 307        # Initialize streaming cache attributes (_feat_map, _conv_idx, etc.)308        # so cached_decode() can be called before any explicit clear_cache().309        self.model.clear_cache()310        if self.encoder_model is not None:311            self.encoder_model.clear_cache()312 313    def generate_noise(self, shape, seed=None, rand_device="cpu",314                        rand_torch_dtype=torch.float32, device=None, torch_dtype=None):315        generator = None if seed is None else torch.Generator(rand_device).manual_seed(seed)316        noise = torch.randn(shape, generator=generator, device=rand_device, dtype=rand_torch_dtype)317        noise = noise.to(dtype=torch_dtype, device=device)318        return noise319 320    def encode_to_latent(self, pixel: torch.Tensor) -> torch.Tensor:321        # pixel: [batch_size, num_channels, num_frames, height, width]322        device, dtype = pixel.device, pixel.dtype323        scale = [self.mean.to(device=device, dtype=dtype),324                 1.0 / self.std.to(device=device, dtype=dtype)]325 326        encode_model = self.encoder_model if self.encoder_model is not None else self.model327        output = [328            encode_model.encode(u.unsqueeze(0), scale).float().squeeze(0)329            for u in pixel330        ]331        output = torch.stack(output, dim=0)332        # from [B, C, F, H, W] to [B, F, C, H, W]333        output = output.permute(0, 2, 1, 3, 4)334        return output335 336    def decode_to_pixel(self, latent: torch.Tensor, use_cache: bool = False,337                        return_in_cpu: bool = False) -> torch.Tensor:338        # from [B, F, C, H, W] to [B, C, F, H, W]339        zs = latent.permute(0, 2, 1, 3, 4)340        if use_cache:341            assert latent.shape[0] == 1, "Batch size must be 1 when using cache"342 343        device, dtype = latent.device, latent.dtype344        scale = [self.mean.to(device=device, dtype=dtype),345                 1.0 / self.std.to(device=device, dtype=dtype)]346 347        if use_cache:348            decode_function = self.model.cached_decode349        else:350            decode_function = self.model.decode351 352        output = []353        for u in zs:354            decoded = decode_function(u.unsqueeze(0), scale).float().clamp_(-1, 1).squeeze(0)355            if return_in_cpu:356                decoded = decoded.cpu()357            output.append(decoded)358        output = torch.stack(output, dim=0)359        # from [B, C, F, H, W] to [B, F, C, H, W]360        output = output.permute(0, 2, 1, 3, 4)361        return output362 363 364def create_vae_from_config(config) -> Optional[torch.nn.Module]:365    """Create a VAE wrapper based on the unified ``vae_type`` config field.366 367    Supported vae_type values:368        - "wan2.2":         Standard Wan2.2 VAE (returns None, pipeline creates WanVAEWrapper)369        - "taew2_2":        TAeW2.2/taehv fast streaming decoder370        - "mg_lightvae":    MG-LightVAE pruned decoder (pruning rate 0.5)371        - "mg_lightvae_v2": MG-LightVAE v2 pruned decoder (pruning rate 0.75)372 373    If ``vae_type`` is not set, defaults to "taew2_2".374 375    Returns:376        A VAE wrapper instance, or None for wan2.2 (let pipeline create377        WanVAEWrapper from vae_kwargs).378    """379    vae_type = getattr(config, "vae_type", None)380 381    if vae_type is None:382        vae_type = "taew2_2"383    else:384        vae_type = str(vae_type).strip().lower()385 386    if vae_type == "wan2.2":387        return None  # pipeline creates WanVAEWrapper(**vae_kwargs) internally388 389    if vae_type == "taew2_2":390        ckpt = os.environ.get("TAEW2_2_CHECKPOINT") or getattr(391            config, "taew2_2_checkpoint", "taew2_2.pth"392        )393        return TAEW2_2VAEWrapper(checkpoint_path=ckpt).eval()394 395    if vae_type in ("mg_lightvae", "mg_lightvae_v2"):396        pruning_map = {"mg_lightvae": 0.5, "mg_lightvae_v2": 0.75}397        # Explicit pruning rate overrides the default mapping398        explicit_rate = getattr(config, "lightvae_pruning_rate", None)399        if explicit_rate is not None:400            pruning_rate = float(explicit_rate)401        else:402            pruning_rate = pruning_map[vae_type]403 404        # Select checkpoint based on vae_type405        ckpt_map = {406            "mg_lightvae": "lightvae_checkpoint",407            "mg_lightvae_v2": "lightvae_v2_checkpoint",408        }409        vae_ckpt = getattr(config, ckpt_map[vae_type], None)410        if vae_ckpt is None:411            raise ValueError(412                f"vae_type={vae_type!r} requires '{ckpt_map[vae_type]}' config field "413                f"(path to MG-LightVAE .pth file)."414            )415 416        # Encoder checkpoint: explicit config, or fall back to vae_kwargs.pretrained_path417        encoder_ckpt = getattr(config, "lightvae_encoder_checkpoint", None)418        if encoder_ckpt is None:419            vae_kwargs = getattr(config, "vae_kwargs", {}) or {}420            if isinstance(vae_kwargs, dict):421                encoder_ckpt = vae_kwargs.get("pretrained_path")422            else:423                encoder_ckpt = getattr(vae_kwargs, "pretrained_path", None)424        if encoder_ckpt is None:425            raise ValueError(426                f"vae_type={vae_type!r} requires 'lightvae_encoder_checkpoint' config field "427                f"(path to full Wan2.2_VAE.pth for teacher encoder), "428                f"or 'vae_kwargs.pretrained_path' must be set."429            )430 431        return MGLightVAEWrapper(432            vae_pth=vae_ckpt,433            lightvae_pruning_rate=pruning_rate,434            lightvae_encoder_vae_pth=encoder_ckpt,435        )436 437    raise ValueError(438        f"Unsupported vae_type={vae_type!r}. "439        f"Choose from: wan2.2, taew2_2, mg_lightvae, mg_lightvae_v2."440    )441 442 443class WanDiffusionWrapper(torch.nn.Module):444    @staticmethod445    def _materialize_meta_tensors(module: torch.nn.Module, device: torch.device = torch.device("cpu")):446        materialized_names = []447 448        def _materialize_recursive(mod: torch.nn.Module, prefix: str = ""):449            for name, param in list(mod.named_parameters(recurse=False)):450                if getattr(param, "is_meta", False):451                    new_param = torch.nn.Parameter(452                        torch.empty(tuple(param.shape), dtype=param.dtype, device=device),453                        requires_grad=param.requires_grad,454                    )455                    setattr(mod, name, new_param)456                    materialized_names.append(prefix + name)457 458            for name, buf in list(mod.named_buffers(recurse=False)):459                if getattr(buf, "is_meta", False):460                    setattr(mod, name, torch.empty(tuple(buf.shape), dtype=buf.dtype, device=device))461                    materialized_names.append(prefix + name)462 463            for child_name, child in mod.named_children():464                _materialize_recursive(child, prefix + child_name + ".")465 466        _materialize_recursive(module)467        return materialized_names468 469    @staticmethod470    def _normalize_model_state_dict_keys(state_dict: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:471        key_prefixes = (472            "generator.model._fsdp_wrapped_module.",473            "generator.model.",474            "model._fsdp_wrapped_module.",475            "model.",476            "_fsdp_wrapped_module.",477            "module.",478        )479        keys = list(state_dict.keys())480        for prefix in key_prefixes:481            if keys and all(k.startswith(prefix) for k in keys):482                return {k[len(prefix):]: v for k, v in state_dict.items()}483        return state_dict484 485    def _load_model_safetensors(self, model_safetensors_path: str) -> None:486        if model_safetensors_path.startswith("oss://"):487            raise ValueError(488                "model_safetensors_path must be a local mounted path on AI-Hub, "489                f"got {model_safetensors_path}"490            )491 492        model_safetensors_path = _resolve_wan_path(model_safetensors_path)493        print(f"[WanDiffusionWrapper] Loading model safetensors from {model_safetensors_path}")494        state_dict = load_safetensors_file(model_safetensors_path, device="cpu")495        state_dict = self._normalize_model_state_dict_keys(state_dict)496 497        model_keys = set(self.model.state_dict().keys())498        matched_keys = model_keys.intersection(state_dict.keys())499        if not matched_keys:500            sample_keys = list(state_dict.keys())[:10]501            raise ValueError(502                "No safetensors keys matched the Wan model state_dict. "503                f"First loaded keys: {sample_keys}"504            )505 506        match_ratio = len(matched_keys) / max(1, len(state_dict))507        if match_ratio < 0.5:508            sample_unexpected = [k for k in state_dict.keys() if k not in model_keys][:10]509            raise ValueError(510                f"Only {len(matched_keys)}/{len(state_dict)} safetensors keys match the Wan model "511                f"state_dict after prefix normalization. Sample unexpected keys: {sample_unexpected}"512            )513 514        missing, unexpected = self.model.load_state_dict(state_dict, strict=False)515        if missing:516            print(517                f"[WanDiffusionWrapper] model_safetensors missing {len(missing)} keys "518                f"(showing first 20): {missing[:20]}"519            )520        if unexpected:521            print(522                f"[WanDiffusionWrapper] model_safetensors unexpected {len(unexpected)} keys "523                f"(showing first 20): {unexpected[:20]}"524            )525        print(526            f"[WanDiffusionWrapper] Loaded model safetensors with {len(matched_keys)} "527            f"matched keys from {model_safetensors_path}"528        )529 530    def __init__(531            self,532            model_name="Wan2.1-T2V-1.3B",533            timestep_shift=8.0,534            is_causal=False,535            local_attn_size=-1,536            sink_size=0,537            subfolder=None,538            model_type='t2v',539            num_frame_per_block=3,540            model_safetensors_path: Optional[str] = None,541            **model_init_kwargs,542    ):543        super().__init__()544 545        self.model_type = model_type546        use_relative_rope = bool(model_init_kwargs.pop("use_relative_rope", False))547        if is_causal:548            model_init_kwargs["use_relative_rope"] = use_relative_rope549            self.model = CausalWanModel.from_pretrained(550                model_name, local_attn_size=local_attn_size, sink_size=sink_size, model_type=model_type, num_frame_per_block=num_frame_per_block,551                **model_init_kwargs)552        else:553            if use_relative_rope:554                print("[WanDiffusionWrapper] use_relative_rope is ignored for non-causal WanModel.")555            self.model = WanModel.from_pretrained(model_name, model_type=model_type, **model_init_kwargs)556        materialized = self._materialize_meta_tensors(self.model, device=torch.device("cpu"))557        if materialized:558            print(f"[WanDiffusionWrapper] Materialized {len(materialized)} meta tensors on CPU.")559        if model_safetensors_path:560            self._load_model_safetensors(model_safetensors_path)561        self.model.eval()562 563        # For non-causal diffusion, all frames share the same timestep564        self.uniform_timestep = not is_causal565 566        self.scheduler = FlowMatchScheduler(567            shift=timestep_shift, sigma_min=0.0, extra_one_step=True568        )569        self.scheduler.set_timesteps(1000, training=True)570 571        self.seq_len = None  # [1, 21, 16, 60, 104]572        self.post_init()573 574    def enable_gradient_checkpointing(self) -> None:575        self.model.enable_gradient_checkpointing()576 577    def adding_cls_branch(self, atten_dim=1536, num_class=4, time_embed_dim=0) -> None:578        # NOTE: This is hard coded for WAN2.1-T2V-1.3B for now!!!!!!!!!!!!!!!!!!!!579        self._cls_pred_branch = nn.Sequential(580            # Input: [B, 384, 21, 60, 104]581            nn.LayerNorm(atten_dim * 3 + time_embed_dim),582            nn.Linear(atten_dim * 3 + time_embed_dim, 1536),583            nn.SiLU(),584            nn.Linear(atten_dim, num_class)585        )586        self._cls_pred_branch.requires_grad_(True)587        num_registers = 3588        self._register_tokens = RegisterTokens(num_registers=num_registers, dim=atten_dim)589        self._register_tokens.requires_grad_(True)590 591        gan_ca_blocks = []592        for _ in range(num_registers):593            block = GanAttentionBlock()594            gan_ca_blocks.append(block)595        self._gan_ca_blocks = nn.ModuleList(gan_ca_blocks)596        self._gan_ca_blocks.requires_grad_(True)597        # self.has_cls_branch = True598 599    def _convert_flow_pred_to_x0(self, flow_pred: torch.Tensor, xt: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:600        """601        Convert flow matching's prediction to x0 prediction.602        flow_pred: the prediction with shape [B, C, H, W]603        xt: the input noisy data with shape [B, C, H, W]604        timestep: the timestep with shape [B]605 606        pred = noise - x0607        x_t = (1-sigma_t) * x0 + sigma_t * noise608        we have x0 = x_t - sigma_t * pred609        see derivations https://chatgpt.com/share/67bf8589-3d04-8008-bc6e-4cf1a24e2d0e610        """611        # use higher precision for calculations612        original_dtype = flow_pred.dtype613        flow_pred, xt, sigmas, timesteps = map(614            lambda x: x.double().to(flow_pred.device), [flow_pred, xt,615                                                        self.scheduler.sigmas,616                                                        self.scheduler.timesteps]617        )618 619        timestep_id = torch.argmin(620            (timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)621        sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)622        x0_pred = xt - sigma_t * flow_pred623        return x0_pred.to(original_dtype)624 625    @staticmethod626    def _convert_x0_to_flow_pred(scheduler, x0_pred: torch.Tensor, xt: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:627        """628        Convert x0 prediction to flow matching's prediction.629        x0_pred: the x0 prediction with shape [B, C, H, W]630        xt: the input noisy data with shape [B, C, H, W]631        timestep: the timestep with shape [B]632 633        pred = (x_t - x_0) / sigma_t634        """635        # use higher precision for calculations636        original_dtype = x0_pred.dtype637        x0_pred, xt, sigmas, timesteps = map(638            lambda x: x.double().to(x0_pred.device), [x0_pred, xt,639                                                      scheduler.sigmas,640                                                      scheduler.timesteps]641        )642        timestep_id = torch.argmin(643            (timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)644        sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)645        flow_pred = (xt - x0_pred) / sigma_t646        return flow_pred.to(original_dtype)647 648    @staticmethod649    def _history_x_to_model_format(history_x):650        if history_x is None:651            return None652 653        if torch.is_tensor(history_x):654            if history_x.ndim != 5:655                raise ValueError(656                    f"history_x must be [B,F,C,H,W] when passed as a tensor, got {history_x.shape}"657                )658            return [u.permute(1, 0, 2, 3).contiguous() for u in history_x]659 660        return history_x661 662    @staticmethod663    def _history_condition_to_model_format(value):664        if value is None:665            return None666 667        if torch.is_tensor(value):668            if value.ndim < 3:669                return value670            return [u.contiguous() for u in value]671 672        return value673 674    def forward(675        self,676        noisy_image_or_video: torch.Tensor, conditional_dict: dict,677        timestep: torch.Tensor, kv_cache: Optional[List[dict]] = None,678        crossattn_cache: Optional[List[dict]] = None,679        current_start: Optional[int] = None,680 681        classify_mode: Optional[bool] = False, # DF682        concat_time_embeddings: Optional[bool] = False, #DF683        clean_x: Optional[torch.Tensor] = None, # TF684        aug_t: Optional[torch.Tensor] = None, # for TF clean GT, if it's also noisy and needs denoising by the model, aug_t is its timestep685 686        cache_start: Optional[int] = None,687        updating_cache: Optional[bool] = False,688        replace_first_timestep_and_noise_latents: Optional[bool] = False,689        history_x: Optional[torch.Tensor] = None,690        history_y: Optional[torch.Tensor] = None,691        history_act_context: Optional[torch.Tensor] = None,692        history_y_action: Optional[torch.Tensor] = None,693        noisy_start_frame: int = 0,694 695    ) -> torch.Tensor:696        prompt_embeds = conditional_dict["prompt_embeds"]697        act_context = conditional_dict.get("act_context", None)698        act_context_scale = conditional_dict.get("act_context_scale", 1.0)699        clip_fea = conditional_dict.get("clip_fea", None)700        y = conditional_dict.get("y", None)701        y_action = conditional_dict.get("y_action", None)702        ref_latents = conditional_dict.get("ref_latents", None)703        ref_mask = conditional_dict.get("ref_mask", None)704        # first_frame_latents = conditional_dict.get("first_frame_latents", None)705 706        raw_timestep = timestep707        b, f, c, h, w = noisy_image_or_video.shape708 709        if replace_first_timestep_and_noise_latents:710            # Wan2.2 5B uses the first latent frame as a clean condition. Keep711            # per-frame timesteps for score models so only frame 0 is forced to t=0.712            if raw_timestep.dim() == 2:713                input_timestep = raw_timestep.clone()714                input_timestep[:, 0] = 0715            elif raw_timestep.dim() == 1 and raw_timestep.shape[0] == f:716                input_timestep = raw_timestep.unsqueeze(0).repeat(b, 1)717                input_timestep[:, 0] = 0718            elif raw_timestep.dim() == 1 and raw_timestep.shape[0] == b:719                input_timestep = raw_timestep[:, None].repeat(1, f)720                input_timestep[:, 0] = 0721            else:722                input_timestep = raw_timestep.reshape(-1)[0].view(1, 1).repeat(b, f)723                input_timestep[:, 0] = 0724        elif self.uniform_timestep:725            # [B, F] -> [B] for legacy non-causal uniform score models.726            input_timestep = raw_timestep[:, 0]727        else:728            input_timestep = raw_timestep729 730        logits = None731        if history_x is not None:732            history_kwargs = {733                "history_x": self._history_x_to_model_format(history_x),734                "noisy_start_frame": int(noisy_start_frame),735            }736            history_y = self._history_condition_to_model_format(history_y)737            history_act_context = self._history_condition_to_model_format(history_act_context)738            history_y_action = self._history_condition_to_model_format(history_y_action)739 740            if history_y is not None:741                history_kwargs["history_y"] = history_y742            if history_act_context is not None:743                history_kwargs["history_act_context"] = history_act_context744            if history_y_action is not None:745                history_kwargs["history_y_action"] = history_y_action746 747            model_out = self.model(748                noisy_image_or_video.permute(0, 2, 1, 3, 4),749                t=input_timestep,750                context=prompt_embeds,751                seq_len=self.seq_len,752                kv_cache=None,753                crossattn_cache=crossattn_cache,754                current_start=0 if current_start is None else current_start,755                cache_start=0 if cache_start is None else cache_start,756                act_context=act_context,757                y_action=y_action,758                act_context_scale=act_context_scale,759                clip_fea=clip_fea,760                y=y,761                ref_latents=ref_latents,762                ref_mask=ref_mask,763                **history_kwargs,764            )765            if isinstance(model_out, tuple):766                flow_pred = model_out[0]767            else:768                flow_pred = model_out769            flow_pred = flow_pred.permute(0, 2, 1, 3, 4)770        # X0 prediction771        elif kv_cache is not None:772            kwargs = {}773            if updating_cache:774                kwargs["updating_cache"] = updating_cache775            flow_pred = self.model(776                noisy_image_or_video.permute(0, 2, 1, 3, 4), # => [B, C, F, H, W],777                t=input_timestep, context=prompt_embeds,778                seq_len=self.seq_len,779                kv_cache=kv_cache,780                crossattn_cache=crossattn_cache,781                current_start=current_start,782                cache_start=cache_start,783                act_context=act_context,784                act_context_scale=act_context_scale,785                clip_fea=clip_fea,786                y=y,787                ref_latents=ref_latents,788                ref_mask=ref_mask,789                **kwargs,790            ).permute(0, 2, 1, 3, 4)791        else:792            if clean_x is not None:793                # teacher forcing794                flow_pred = self.model(795                    noisy_image_or_video.permute(0, 2, 1, 3, 4), # => [B, C, F, H, W]796                    t=input_timestep, context=prompt_embeds,797                    seq_len=self.seq_len,798                    clean_x=clean_x.permute(0, 2, 1, 3, 4), # => [B, C, F, H, W]799                    aug_t=aug_t,800                    act_context=act_context,801                    act_context_scale=act_context_scale,802                    clip_fea=clip_fea,803                    y=y,804                    ref_latents=ref_latents,805                    ref_mask=ref_mask,806                ).permute(0, 2, 1, 3, 4)807            else:808                # diffusion forcing or bidirectional809                if classify_mode:810                    flow_pred, logits = self.model(811                        noisy_image_or_video.permute(0, 2, 1, 3, 4),812                        t=input_timestep, context=prompt_embeds,813                        seq_len=self.seq_len,814                        classify_mode=True,815                        register_tokens=self._register_tokens,816                        cls_pred_branch=self._cls_pred_branch,817                        gan_ca_blocks=self._gan_ca_blocks,818                        concat_time_embeddings=concat_time_embeddings,819                        act_context=act_context,820                        act_context_scale=act_context_scale,821                        clip_fea=clip_fea,822                        y=y,823                        ref_latents=ref_latents,824                        ref_mask=ref_mask,825                    )826                    flow_pred = flow_pred.permute(0, 2, 1, 3, 4)827                else:828                    flow_pred = self.model(829                        noisy_image_or_video.permute(0, 2, 1, 3, 4),830                        t=input_timestep, context=prompt_embeds,831                        seq_len=self.seq_len,832                        act_context=act_context,833                        act_context_scale=act_context_scale,834                        clip_fea=clip_fea,835                        y=y,836                        ref_latents=ref_latents,837                        ref_mask=ref_mask,838                    ).permute(0, 2, 1, 3, 4)839 840        pred_x0 = self._convert_flow_pred_to_x0(841            flow_pred=flow_pred.flatten(0, 1),842            xt=noisy_image_or_video.flatten(0, 1),843            timestep=timestep.flatten(0, 1)844        ).unflatten(0, flow_pred.shape[:2])845 846        if logits is not None:847            return flow_pred, pred_x0, logits848 849        return flow_pred, pred_x0850 851    def get_scheduler(self) -> SchedulerInterface:852        """853        Update the current scheduler with the interface's static method854        """855        scheduler = self.scheduler856        scheduler.convert_x0_to_noise = types.MethodType(857            SchedulerInterface.convert_x0_to_noise, scheduler)858        scheduler.convert_noise_to_x0 = types.MethodType(859            SchedulerInterface.convert_noise_to_x0, scheduler)860        scheduler.convert_velocity_to_x0 = types.MethodType(861            SchedulerInterface.convert_velocity_to_x0, scheduler)862        self.scheduler = scheduler863        return scheduler864 865    def post_init(self):866        """867        A few custom initialization steps that should be called after the object is created.868        Currently, the only one we have is to bind a few methods to scheduler.869        We can gradually add more methods here if needed.870        """871        self.get_scheduler()872