CoolFace
Apppublic

ALSv/self-forcing

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
text2video.py267 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 torch13import torch.cuda.amp as amp14import torch.distributed as dist15from tqdm import tqdm16 17from .distributed.fsdp import shard_model18from .modules.model import WanModel19from .modules.t5 import T5EncoderModel20from .modules.vae import WanVAE21from .utils.fm_solvers import (FlowDPMSolverMultistepScheduler,22                               get_sampling_sigmas, retrieve_timesteps)23from .utils.fm_solvers_unipc import FlowUniPCMultistepScheduler24 25 26class WanT2V:27 28    def __init__(29        self,30        config,31        checkpoint_dir,32        device_id=0,33        rank=0,34        t5_fsdp=False,35        dit_fsdp=False,36        use_usp=False,37        t5_cpu=False,38    ):39        r"""40        Initializes the Wan text-to-video generation model components.41 42        Args:43            config (EasyDict):44                Object containing model parameters initialized from config.py45            checkpoint_dir (`str`):46                Path to directory containing model checkpoints47            device_id (`int`,  *optional*, defaults to 0):48                Id of target GPU device49            rank (`int`,  *optional*, defaults to 0):50                Process rank for distributed training51            t5_fsdp (`bool`, *optional*, defaults to False):52                Enable FSDP sharding for T5 model53            dit_fsdp (`bool`, *optional*, defaults to False):54                Enable FSDP sharding for DiT model55            use_usp (`bool`, *optional*, defaults to False):56                Enable distribution strategy of USP.57            t5_cpu (`bool`, *optional*, defaults to False):58                Whether to place T5 model on CPU. Only works without t5_fsdp.59        """60        self.device = torch.device(f"cuda:{device_id}")61        self.config = config62        self.rank = rank63        self.t5_cpu = t5_cpu64 65        self.num_train_timesteps = config.num_train_timesteps66        self.param_dtype = config.param_dtype67 68        shard_fn = partial(shard_model, device_id=device_id)69        self.text_encoder = T5EncoderModel(70            text_len=config.text_len,71            dtype=config.t5_dtype,72            device=torch.device('cpu'),73            checkpoint_path=os.path.join(checkpoint_dir, config.t5_checkpoint),74            tokenizer_path=os.path.join(checkpoint_dir, config.t5_tokenizer),75            shard_fn=shard_fn if t5_fsdp else None)76 77        self.vae_stride = config.vae_stride78        self.patch_size = config.patch_size79        self.vae = WanVAE(80            vae_pth=os.path.join(checkpoint_dir, config.vae_checkpoint),81            device=self.device)82 83        logging.info(f"Creating WanModel from {checkpoint_dir}")84        self.model = WanModel.from_pretrained(checkpoint_dir)85        self.model.eval().requires_grad_(False)86 87        if use_usp:88            from xfuser.core.distributed import \89                get_sequence_parallel_world_size90 91            from .distributed.xdit_context_parallel import (usp_attn_forward,92                                                            usp_dit_forward)93            for block in self.model.blocks:94                block.self_attn.forward = types.MethodType(95                    usp_attn_forward, block.self_attn)96            self.model.forward = types.MethodType(usp_dit_forward, self.model)97            self.sp_size = get_sequence_parallel_world_size()98        else:99            self.sp_size = 1100 101        if dist.is_initialized():102            dist.barrier()103        if dit_fsdp:104            self.model = shard_fn(self.model)105        else:106            self.model.to(self.device)107 108        self.sample_neg_prompt = config.sample_neg_prompt109 110    def generate(self,111                 input_prompt,112                 size=(1280, 720),113                 frame_num=81,114                 shift=5.0,115                 sample_solver='unipc',116                 sampling_steps=50,117                 guide_scale=5.0,118                 n_prompt="",119                 seed=-1,120                 offload_model=True):121        r"""122        Generates video frames from text prompt using diffusion process.123 124        Args:125            input_prompt (`str`):126                Text prompt for content generation127            size (tupele[`int`], *optional*, defaults to (1280,720)):128                Controls video resolution, (width,height).129            frame_num (`int`, *optional*, defaults to 81):130                How many frames to sample from a video. The number should be 4n+1131            shift (`float`, *optional*, defaults to 5.0):132                Noise schedule shift parameter. Affects temporal dynamics133            sample_solver (`str`, *optional*, defaults to 'unipc'):134                Solver used to sample the video.135            sampling_steps (`int`, *optional*, defaults to 40):136                Number of diffusion sampling steps. Higher values improve quality but slow generation137            guide_scale (`float`, *optional*, defaults 5.0):138                Classifier-free guidance scale. Controls prompt adherence vs. creativity139            n_prompt (`str`, *optional*, defaults to ""):140                Negative prompt for content exclusion. If not given, use `config.sample_neg_prompt`141            seed (`int`, *optional*, defaults to -1):142                Random seed for noise generation. If -1, use random seed.143            offload_model (`bool`, *optional*, defaults to True):144                If True, offloads models to CPU during generation to save VRAM145 146        Returns:147            torch.Tensor:148                Generated video frames tensor. Dimensions: (C, N H, W) where:149                - C: Color channels (3 for RGB)150                - N: Number of frames (81)151                - H: Frame height (from size)152                - W: Frame width from size)153        """154        # preprocess155        F = frame_num156        target_shape = (self.vae.model.z_dim, (F - 1) // self.vae_stride[0] + 1,157                        size[1] // self.vae_stride[1],158                        size[0] // self.vae_stride[2])159 160        seq_len = math.ceil((target_shape[2] * target_shape[3]) /161                            (self.patch_size[1] * self.patch_size[2]) *162                            target_shape[1] / self.sp_size) * self.sp_size163 164        if n_prompt == "":165            n_prompt = self.sample_neg_prompt166        seed = seed if seed >= 0 else random.randint(0, sys.maxsize)167        seed_g = torch.Generator(device=self.device)168        seed_g.manual_seed(seed)169 170        if not self.t5_cpu:171            self.text_encoder.model.to(self.device)172            context = self.text_encoder([input_prompt], self.device)173            context_null = self.text_encoder([n_prompt], self.device)174            if offload_model:175                self.text_encoder.model.cpu()176        else:177            context = self.text_encoder([input_prompt], torch.device('cpu'))178            context_null = self.text_encoder([n_prompt], torch.device('cpu'))179            context = [t.to(self.device) for t in context]180            context_null = [t.to(self.device) for t in context_null]181 182        noise = [183            torch.randn(184                target_shape[0],185                target_shape[1],186                target_shape[2],187                target_shape[3],188                dtype=torch.float32,189                device=self.device,190                generator=seed_g)191        ]192 193        @contextmanager194        def noop_no_sync():195            yield196 197        no_sync = getattr(self.model, 'no_sync', noop_no_sync)198 199        # evaluation mode200        with amp.autocast(dtype=self.param_dtype), torch.no_grad(), no_sync():201 202            if sample_solver == 'unipc':203                sample_scheduler = FlowUniPCMultistepScheduler(204                    num_train_timesteps=self.num_train_timesteps,205                    shift=1,206                    use_dynamic_shifting=False)207                sample_scheduler.set_timesteps(208                    sampling_steps, device=self.device, shift=shift)209                timesteps = sample_scheduler.timesteps210            elif sample_solver == 'dpm++':211                sample_scheduler = FlowDPMSolverMultistepScheduler(212                    num_train_timesteps=self.num_train_timesteps,213                    shift=1,214                    use_dynamic_shifting=False)215                sampling_sigmas = get_sampling_sigmas(sampling_steps, shift)216                timesteps, _ = retrieve_timesteps(217                    sample_scheduler,218                    device=self.device,219                    sigmas=sampling_sigmas)220            else:221                raise NotImplementedError("Unsupported solver.")222 223            # sample videos224            latents = noise225 226            arg_c = {'context': context, 'seq_len': seq_len}227            arg_null = {'context': context_null, 'seq_len': seq_len}228 229            for _, t in enumerate(tqdm(timesteps)):230                latent_model_input = latents231                timestep = [t]232 233                timestep = torch.stack(timestep)234 235                self.model.to(self.device)236                noise_pred_cond = self.model(237                    latent_model_input, t=timestep, **arg_c)[0]238                noise_pred_uncond = self.model(239                    latent_model_input, t=timestep, **arg_null)[0]240 241                noise_pred = noise_pred_uncond + guide_scale * (242                    noise_pred_cond - noise_pred_uncond)243 244                temp_x0 = sample_scheduler.step(245                    noise_pred.unsqueeze(0),246                    t,247                    latents[0].unsqueeze(0),248                    return_dict=False,249                    generator=seed_g)[0]250                latents = [temp_x0.squeeze(0)]251 252            x0 = latents253            if offload_model:254                self.model.cpu()255            if self.rank == 0:256                videos = self.vae.decode(x0)257 258        del noise, latents259        del sample_scheduler260        if offload_model:261            gc.collect()262            torch.cuda.synchronize()263        if dist.is_initialized():264            dist.barrier()265 266        return videos[0] if self.rank == 0 else None267