CoolFace
Apppublic

ALSv/self-forcing

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
image2video.py348 linesDownload Raw Back to wan
1# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.2import gc3import logging4import math5import os6import random7import sys8import types9from contextlib import contextmanager10from functools import partial11 12import numpy as np13import torch14import torch.cuda.amp as amp15import torch.distributed as dist16import torchvision.transforms.functional as TF17from tqdm import tqdm18 19from .distributed.fsdp import shard_model20from .modules.clip import CLIPModel21from .modules.model import WanModel22from .modules.t5 import T5EncoderModel23from .modules.vae import WanVAE24from .utils.fm_solvers import (FlowDPMSolverMultistepScheduler,25                               get_sampling_sigmas, retrieve_timesteps)26from .utils.fm_solvers_unipc import FlowUniPCMultistepScheduler27 28 29class WanI2V:30 31    def __init__(32        self,33        config,34        checkpoint_dir,35        device_id=0,36        rank=0,37        t5_fsdp=False,38        dit_fsdp=False,39        use_usp=False,40        t5_cpu=False,41        init_on_cpu=True,42    ):43        r"""44        Initializes the image-to-video generation model components.45 46        Args:47            config (EasyDict):48                Object containing model parameters initialized from config.py49            checkpoint_dir (`str`):50                Path to directory containing model checkpoints51            device_id (`int`,  *optional*, defaults to 0):52                Id of target GPU device53            rank (`int`,  *optional*, defaults to 0):54                Process rank for distributed training55            t5_fsdp (`bool`, *optional*, defaults to False):56                Enable FSDP sharding for T5 model57            dit_fsdp (`bool`, *optional*, defaults to False):58                Enable FSDP sharding for DiT model59            use_usp (`bool`, *optional*, defaults to False):60                Enable distribution strategy of USP.61            t5_cpu (`bool`, *optional*, defaults to False):62                Whether to place T5 model on CPU. Only works without t5_fsdp.63            init_on_cpu (`bool`, *optional*, defaults to True):64                Enable initializing Transformer Model on CPU. Only works without FSDP or USP.65        """66        self.device = torch.device(f"cuda:{device_id}")67        self.config = config68        self.rank = rank69        self.use_usp = use_usp70        self.t5_cpu = t5_cpu71 72        self.num_train_timesteps = config.num_train_timesteps73        self.param_dtype = config.param_dtype74 75        shard_fn = partial(shard_model, device_id=device_id)76        self.text_encoder = T5EncoderModel(77            text_len=config.text_len,78            dtype=config.t5_dtype,79            device=torch.device('cpu'),80            checkpoint_path=os.path.join(checkpoint_dir, config.t5_checkpoint),81            tokenizer_path=os.path.join(checkpoint_dir, config.t5_tokenizer),82            shard_fn=shard_fn if t5_fsdp else None,83        )84 85        self.vae_stride = config.vae_stride86        self.patch_size = config.patch_size87        self.vae = WanVAE(88            vae_pth=os.path.join(checkpoint_dir, config.vae_checkpoint),89            device=self.device)90 91        self.clip = CLIPModel(92            dtype=config.clip_dtype,93            device=self.device,94            checkpoint_path=os.path.join(checkpoint_dir,95                                         config.clip_checkpoint),96            tokenizer_path=os.path.join(checkpoint_dir, config.clip_tokenizer))97 98        logging.info(f"Creating WanModel from {checkpoint_dir}")99        self.model = WanModel.from_pretrained(checkpoint_dir)100        self.model.eval().requires_grad_(False)101 102        if t5_fsdp or dit_fsdp or use_usp:103            init_on_cpu = False104 105        if use_usp:106            from xfuser.core.distributed import \107                get_sequence_parallel_world_size108 109            from .distributed.xdit_context_parallel import (usp_attn_forward,110                                                            usp_dit_forward)111            for block in self.model.blocks:112                block.self_attn.forward = types.MethodType(113                    usp_attn_forward, block.self_attn)114            self.model.forward = types.MethodType(usp_dit_forward, self.model)115            self.sp_size = get_sequence_parallel_world_size()116        else:117            self.sp_size = 1118 119        if dist.is_initialized():120            dist.barrier()121        if dit_fsdp:122            self.model = shard_fn(self.model)123        else:124            if not init_on_cpu:125                self.model.to(self.device)126 127        self.sample_neg_prompt = config.sample_neg_prompt128 129    def generate(self,130                 input_prompt,131                 img,132                 max_area=720 * 1280,133                 frame_num=81,134                 shift=5.0,135                 sample_solver='unipc',136                 sampling_steps=40,137                 guide_scale=5.0,138                 n_prompt="",139                 seed=-1,140                 offload_model=True):141        r"""142        Generates video frames from input image and text prompt using diffusion process.143 144        Args:145            input_prompt (`str`):146                Text prompt for content generation.147            img (PIL.Image.Image):148                Input image tensor. Shape: [3, H, W]149            max_area (`int`, *optional*, defaults to 720*1280):150                Maximum pixel area for latent space calculation. Controls video resolution scaling151            frame_num (`int`, *optional*, defaults to 81):152                How many frames to sample from a video. The number should be 4n+1153            shift (`float`, *optional*, defaults to 5.0):154                Noise schedule shift parameter. Affects temporal dynamics155                [NOTE]: If you want to generate a 480p video, it is recommended to set the shift value to 3.0.156            sample_solver (`str`, *optional*, defaults to 'unipc'):157                Solver used to sample the video.158            sampling_steps (`int`, *optional*, defaults to 40):159                Number of diffusion sampling steps. Higher values improve quality but slow generation160            guide_scale (`float`, *optional*, defaults 5.0):161                Classifier-free guidance scale. Controls prompt adherence vs. creativity162            n_prompt (`str`, *optional*, defaults to ""):163                Negative prompt for content exclusion. If not given, use `config.sample_neg_prompt`164            seed (`int`, *optional*, defaults to -1):165                Random seed for noise generation. If -1, use random seed166            offload_model (`bool`, *optional*, defaults to True):167                If True, offloads models to CPU during generation to save VRAM168 169        Returns:170            torch.Tensor:171                Generated video frames tensor. Dimensions: (C, N H, W) where:172                - C: Color channels (3 for RGB)173                - N: Number of frames (81)174                - H: Frame height (from max_area)175                - W: Frame width from max_area)176        """177        img = TF.to_tensor(img).sub_(0.5).div_(0.5).to(self.device)178 179        F = frame_num180        h, w = img.shape[1:]181        aspect_ratio = h / w182        lat_h = round(183            np.sqrt(max_area * aspect_ratio) // self.vae_stride[1] //184            self.patch_size[1] * self.patch_size[1])185        lat_w = round(186            np.sqrt(max_area / aspect_ratio) // self.vae_stride[2] //187            self.patch_size[2] * self.patch_size[2])188        h = lat_h * self.vae_stride[1]189        w = lat_w * self.vae_stride[2]190 191        max_seq_len = ((F - 1) // self.vae_stride[0] + 1) * lat_h * lat_w // (192            self.patch_size[1] * self.patch_size[2])193        max_seq_len = int(math.ceil(max_seq_len / self.sp_size)) * self.sp_size194 195        seed = seed if seed >= 0 else random.randint(0, sys.maxsize)196        seed_g = torch.Generator(device=self.device)197        seed_g.manual_seed(seed)198        noise = torch.randn(199            16,200            21,201            lat_h,202            lat_w,203            dtype=torch.float32,204            generator=seed_g,205            device=self.device)206 207        msk = torch.ones(1, 81, lat_h, lat_w, device=self.device)208        msk[:, 1:] = 0209        msk = torch.concat([210            torch.repeat_interleave(msk[:, 0:1], repeats=4, dim=1), msk[:, 1:]211        ],212            dim=1)213        msk = msk.view(1, msk.shape[1] // 4, 4, lat_h, lat_w)214        msk = msk.transpose(1, 2)[0]215 216        if n_prompt == "":217            n_prompt = self.sample_neg_prompt218 219        # preprocess220        if not self.t5_cpu:221            self.text_encoder.model.to(self.device)222            context = self.text_encoder([input_prompt], self.device)223            context_null = self.text_encoder([n_prompt], self.device)224            if offload_model:225                self.text_encoder.model.cpu()226        else:227            context = self.text_encoder([input_prompt], torch.device('cpu'))228            context_null = self.text_encoder([n_prompt], torch.device('cpu'))229            context = [t.to(self.device) for t in context]230            context_null = [t.to(self.device) for t in context_null]231 232        self.clip.model.to(self.device)233        clip_context = self.clip.visual([img[:, None, :, :]])234        if offload_model:235            self.clip.model.cpu()236 237        y = self.vae.encode([238            torch.concat([239                torch.nn.functional.interpolate(240                    img[None].cpu(), size=(h, w), mode='bicubic').transpose(241                        0, 1),242                torch.zeros(3, 80, h, w)243            ],244                dim=1).to(self.device)245        ])[0]246        y = torch.concat([msk, y])247 248        @contextmanager249        def noop_no_sync():250            yield251 252        no_sync = getattr(self.model, 'no_sync', noop_no_sync)253 254        # evaluation mode255        with amp.autocast(dtype=self.param_dtype), torch.no_grad(), no_sync():256 257            if sample_solver == 'unipc':258                sample_scheduler = FlowUniPCMultistepScheduler(259                    num_train_timesteps=self.num_train_timesteps,260                    shift=1,261                    use_dynamic_shifting=False)262                sample_scheduler.set_timesteps(263                    sampling_steps, device=self.device, shift=shift)264                timesteps = sample_scheduler.timesteps265            elif sample_solver == 'dpm++':266                sample_scheduler = FlowDPMSolverMultistepScheduler(267                    num_train_timesteps=self.num_train_timesteps,268                    shift=1,269                    use_dynamic_shifting=False)270                sampling_sigmas = get_sampling_sigmas(sampling_steps, shift)271                timesteps, _ = retrieve_timesteps(272                    sample_scheduler,273                    device=self.device,274                    sigmas=sampling_sigmas)275            else:276                raise NotImplementedError("Unsupported solver.")277 278            # sample videos279            latent = noise280 281            arg_c = {282                'context': [context[0]],283                'clip_fea': clip_context,284                'seq_len': max_seq_len,285                'y': [y],286            }287 288            arg_null = {289                'context': context_null,290                'clip_fea': clip_context,291                'seq_len': max_seq_len,292                'y': [y],293            }294 295            if offload_model:296                torch.cuda.empty_cache()297 298            self.model.to(self.device)299            for _, t in enumerate(tqdm(timesteps)):300                latent_model_input = [latent.to(self.device)]301                timestep = [t]302 303                timestep = torch.stack(timestep).to(self.device)304 305                noise_pred_cond = self.model(306                    latent_model_input, t=timestep, **arg_c)[0].to(307                        torch.device('cpu') if offload_model else self.device)308                if offload_model:309                    torch.cuda.empty_cache()310                noise_pred_uncond = self.model(311                    latent_model_input, t=timestep, **arg_null)[0].to(312                        torch.device('cpu') if offload_model else self.device)313                if offload_model:314                    torch.cuda.empty_cache()315                noise_pred = noise_pred_uncond + guide_scale * (316                    noise_pred_cond - noise_pred_uncond)317 318                latent = latent.to(319                    torch.device('cpu') if offload_model else self.device)320 321                temp_x0 = sample_scheduler.step(322                    noise_pred.unsqueeze(0),323                    t,324                    latent.unsqueeze(0),325                    return_dict=False,326                    generator=seed_g)[0]327                latent = temp_x0.squeeze(0)328 329                x0 = [latent.to(self.device)]330                del latent_model_input, timestep331 332            if offload_model:333                self.model.cpu()334                torch.cuda.empty_cache()335 336            if self.rank == 0:337                videos = self.vae.decode(x0)338 339        del noise, latent340        del sample_scheduler341        if offload_model:342            gc.collect()343            torch.cuda.synchronize()344        if dist.is_initialized():345            dist.barrier()346 347        return videos[0] if self.rank == 0 else None348