from models import ( get_diffusion_wrapper, get_text_encoder_wrapper, get_vae_wrapper ) from models.wan.taehv_wrapper import TAEHVWanVAEWrapper from typing import List import torch import torch.distributed as dist import logging LOGGER = logging.getLogger(__name__) class CausalStreamInferencePipeline(torch.nn.Module): def __init__(self, args, device): super().__init__() model_type = args.model_type self.device = device # Step 1: Initialize all models self.generator_model_name = getattr( args, "generator_name", args.model_name) self.generator = get_diffusion_wrapper( model_name=self.generator_model_name)(model_type=model_type) self.text_encoder = get_text_encoder_wrapper( model_name=args.model_name)(model_type=model_type) if getattr(args, "use_taehv", False): LOGGER.info("Using TAEHV VAE wrapper for Wan inference") self.vae = TAEHVWanVAEWrapper( model_type=model_type, checkpoint_path=getattr(args, "taehv_checkpoint_path", None), use_tensorrt=getattr(args, "use_tensorrt", False), ) else: self.vae = get_vae_wrapper(model_name=args.model_name)(model_type=model_type) # Step 2: Initialize all causal hyperparmeters self._init_denoising_step_list(args, device) if model_type == "T2V-1.3B": self.num_transformer_blocks = 30 self.num_heads = 12 elif model_type == "T2V-14B": self.num_transformer_blocks = 40 self.num_heads = 40 else: raise ValueError(f"Model type {model_type} not supported") scale_size = 16 self.height = args.height//scale_size*2 self.width = args.width//scale_size*2 self.frame_seq_length = (args.height//scale_size) * (args.width//scale_size) self.num_kv_cache = args.num_kv_cache self.kv_cache_length = self.frame_seq_length*self.num_kv_cache self.num_sink_tokens = args.num_sink_tokens self.adapt_sink_threshold = args.adapt_sink_threshold self.conditional_dict = None self.kv_cache1 = None self.kv_cache2 = None self.hidden_states = None self.block_x = None self.args = args self.num_frame_per_block = getattr( args, "num_frame_per_block", 1) LOGGER.info("KV inference with %s frames per block", self.num_frame_per_block) if self.num_frame_per_block > 1: self.generator.model.num_frame_per_block = self.num_frame_per_block self.generator.model.to(self.device) def _init_denoising_step_list(self, args, device): self.denoising_step_list = torch.tensor( args.denoising_step_list, dtype=torch.long, device=device) assert self.denoising_step_list[-1] == 0 if not args.t2v: # remove the last timestep (which equals zero) self.denoising_step_list = self.denoising_step_list[:-1] self.scheduler = self.generator.get_scheduler() if args.warp_denoising_step: # Warp the denoising step according to the scheduler time shift timesteps = torch.cat( (self.scheduler.timesteps.cpu(), torch.tensor([0], dtype=torch.float32)) ).to(device) self.denoising_step_list = timesteps[1000 - self.denoising_step_list] def _initialize_kv_cache(self, batch_size, dtype, device): """ Initialize a Per-GPU KV cache for the Wan model. """ kv_cache1 = [] for i in range(self.num_transformer_blocks): cache_length = self.kv_cache_length self.generator.model.blocks[i].self_attn.sink_size = self.num_sink_tokens self.generator.model.blocks[i].self_attn.adapt_sink_thr = self.adapt_sink_threshold kv_cache1.append({ "k": torch.zeros([batch_size, cache_length, self.num_heads, 128], dtype=dtype, device=device), "v": torch.zeros([batch_size, cache_length, self.num_heads, 128], dtype=dtype, device=device), "global_end_index": torch.tensor([0], dtype=torch.long, device=device), "local_end_index": torch.tensor([0], dtype=torch.long, device=device), "total_steps": len(self.denoising_step_list), "current_step": len(self.denoising_step_list), }) self.kv_cache1 = kv_cache1 # always store the clean cache def _initialize_crossattn_cache(self, batch_size, dtype, device): """ Initialize a Per-GPU cross-attention cache for the Wan model. """ crossattn_cache = [] for _ in range(self.num_transformer_blocks): crossattn_cache.append({ "k": torch.zeros([batch_size, 512, self.num_heads, 128], dtype=dtype, device=device), "v": torch.zeros([batch_size, 512, self.num_heads, 128], dtype=dtype, device=device), "is_init": False, }) self.crossattn_cache = crossattn_cache # always store the clean cache def prepare( self, text_prompts: List[str], device: torch.device, dtype: torch.dtype, block_mode: str='input', noise: torch.Tensor = None, current_start: int = 0, current_end: int = None, block_num: torch.Tensor = None, batch_denoise: bool=True, ): self.device = device batch_size = noise.shape[0] self.conditional_dict = self.text_encoder( text_prompts=text_prompts ) # Step 1: Initialize KV cache if self.kv_cache1 is None: self._initialize_kv_cache( batch_size=batch_size, dtype=dtype, device=device ) self._initialize_crossattn_cache( batch_size=batch_size, dtype=dtype, device=device ) else: # reset cross attn cache for block_index in range(self.num_transformer_blocks): self.crossattn_cache[block_index]["is_init"] = False current_start = torch.tensor([current_start], dtype=torch.long, device=device) current_end = torch.tensor([current_end], dtype=torch.long, device=device) for index, current_timestep in enumerate(self.denoising_step_list): # set current timestep timestep = torch.ones( [batch_size, noise.shape[1]], device=noise.device, dtype=torch.int64) * current_timestep if index < len(self.denoising_step_list) - 1: denoised_pred = self.generator( noisy_image_or_video=noise, conditional_dict=self.conditional_dict, timestep=timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=current_start, current_end=current_end ) next_timestep = self.denoising_step_list[index + 1] noise = self.scheduler.add_noise( denoised_pred.flatten(0, 1), torch.randn_like(denoised_pred.flatten(0, 1)), next_timestep * torch.ones([batch_size], device=noise.device, dtype=torch.long) ).unflatten(0, denoised_pred.shape[:2]) else: # for getting real output denoised_pred = self.generator( noisy_image_or_video=noise, conditional_dict=self.conditional_dict, timestep=timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=current_start, current_end=current_end ) if not batch_denoise: return denoised_pred # Pre-allocate hidden_states tensor to avoid memory allocation during inference self.batch_size = len(self.denoising_step_list) # Determine which blocks to keep based on block_num range blocks_to_keep = [] if block_num is not None: start_block, end_block = block_num[0].item(), block_num[1].item() blocks_to_keep = list(range(start_block, end_block)) else: blocks_to_keep = list(range(self.num_transformer_blocks)) # Process only the blocks in the specified range for i in range(self.num_transformer_blocks): if dist.is_initialized(): dist.broadcast(self.crossattn_cache[i]['k'], src=0) dist.broadcast(self.crossattn_cache[i]['v'], src=0) dist.broadcast(self.kv_cache1[i]['k'], src=0) dist.broadcast(self.kv_cache1[i]['v'], src=0) self.kv_cache1[i]['k'] = self.kv_cache1[i]['k'].repeat(self.batch_size, 1, 1, 1) self.kv_cache1[i]['v'] = self.kv_cache1[i]['v'].repeat(self.batch_size, 1, 1, 1) self.kv_cache1[i]['global_end_index'] = self.kv_cache1[i]['global_end_index'].repeat(self.batch_size) self.kv_cache1[i]['local_end_index'] = self.kv_cache1[i]['local_end_index'].repeat(self.batch_size) self.crossattn_cache[i]['k'] = self.crossattn_cache[i]['k'].expand(self.batch_size, -1, -1, -1) self.crossattn_cache[i]['v'] = self.crossattn_cache[i]['v'].expand(self.batch_size, -1, -1, -1) # Remove blocks outside the range if block_num is not None: for i in range(self.num_transformer_blocks): if i not in blocks_to_keep: self.kv_cache1[i]['k'] = self.kv_cache1[i]['k'].cpu() self.kv_cache1[i]['v'] = self.kv_cache1[i]['v'].cpu() self.hidden_states = torch.zeros( (self.batch_size, self.num_frame_per_block, *noise.shape[2:]), dtype=noise.dtype, device=device ) if block_mode in ['output', 'middle']: self.block_x = torch.zeros( (self.batch_size, self.frame_seq_length, self.num_heads*128), dtype=noise.dtype, device=device ) else: self.block_x = None self.kv_cache_starts = torch.ones(self.batch_size, dtype=torch.long, device=device) * current_end self.kv_cache_ends = torch.ones(self.batch_size, dtype=torch.long, device=device) * current_end + self.frame_seq_length self.timestep = self.denoising_step_list self.conditional_dict['prompt_embeds'] = self.conditional_dict['prompt_embeds'].repeat(self.batch_size, 1, 1) return denoised_pred def inference_stream( self, noise: torch.Tensor, current_start: int, current_end: int, current_step: int, ) -> torch.Tensor: self.hidden_states[1:] = self.hidden_states[:-1].clone() self.hidden_states[0] = noise[0] self.kv_cache_starts[1:] = self.kv_cache_starts[:-1].clone() self.kv_cache_starts[0] = current_start self.kv_cache_ends[1:] = self.kv_cache_ends[:-1].clone() self.kv_cache_ends[0] = current_end if current_step is not None: self.timestep[0] = current_step self.hidden_states = self.generator( noisy_image_or_video=self.hidden_states, conditional_dict=self.conditional_dict, timestep=self.timestep.unsqueeze(1).expand(-1, self.hidden_states.shape[1]), kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=self.kv_cache_starts, current_end=self.kv_cache_ends, ) for i in range(len(self.denoising_step_list) - 1): self.hidden_states[[i]] = self.scheduler.add_noise( self.hidden_states[[i]], torch.randn_like(self.hidden_states[[i]]), self.denoising_step_list[i + 1] * torch.ones([1], device=self.hidden_states.device, dtype=torch.long) ) return self.hidden_states def inference_wo_batch( self, noise: torch.Tensor, current_start: int, current_end: int, current_step: int, ) -> torch.Tensor: batch_size = noise.shape[0] current_start = torch.ones(batch_size, dtype=torch.long, device=self.device) * current_start current_end = torch.ones(batch_size, dtype=torch.long, device=self.device) * current_end # Step 2.1: Spatial denoising loop self.denoising_step_list[0] = current_step for index, current_timestep in enumerate(self.denoising_step_list): # set current timestep timestep = torch.ones( [batch_size, noise.shape[1]], device=noise.device, dtype=torch.int64) * current_timestep if index < len(self.denoising_step_list) - 1: denoised_pred = self.generator( noisy_image_or_video=noise, conditional_dict=self.conditional_dict, timestep=timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=current_start, current_end=current_end ) next_timestep = self.denoising_step_list[index + 1] noise = self.scheduler.add_noise( denoised_pred.flatten(0, 1), torch.randn_like(denoised_pred.flatten(0, 1)), next_timestep * torch.ones([batch_size], device=noise.device, dtype=torch.long) ).unflatten(0, denoised_pred.shape[:2]) else: # for getting real output denoised_pred = self.generator( noisy_image_or_video=noise, conditional_dict=self.conditional_dict, timestep=timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=current_start, current_end=current_end ) return denoised_pred def inference( self, noise: torch.Tensor, current_start: int, current_end: int, current_step: int, block_mode: str='input', block_num=None, patched_x_shape: torch.Tensor=None, block_x: torch.Tensor=None, ) -> torch.Tensor: if block_mode == 'input': self.hidden_states[1:] = self.hidden_states[:-1].clone() self.hidden_states[0] = noise[0] self.kv_cache_starts[1:] = self.kv_cache_starts[:-1].clone() self.kv_cache_starts[0] = current_start self.kv_cache_ends[1:] = self.kv_cache_ends[:-1].clone() self.kv_cache_ends[0] = current_end else: self.block_x.copy_(block_x) self.hidden_states.copy_(noise) self.kv_cache_starts.copy_(current_start) self.kv_cache_ends.copy_(current_end) if current_step is not None: self.timestep[0] = current_step if block_mode == 'output': denoised_pred = self.generator.forward_output( noisy_image_or_video=self.hidden_states, conditional_dict=self.conditional_dict, timestep=self.timestep.unsqueeze(1).expand(-1, self.hidden_states.shape[1]), kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=self.kv_cache_starts, current_end=self.kv_cache_ends, block_mode=block_mode, block_num=block_num, patched_x_shape=patched_x_shape, block_x=self.block_x ) for i in range(len(self.denoising_step_list) - 1): denoised_pred[[i]] = self.scheduler.add_noise( denoised_pred[[i]], torch.randn_like(denoised_pred[[i]]), self.denoising_step_list[i + 1] * torch.ones([1], device=denoised_pred.device, dtype=torch.long) ) patched_x_shape = None else: denoised_pred, patched_x_shape = self.generator.forward_input( noisy_image_or_video=self.hidden_states, conditional_dict=self.conditional_dict, timestep=self.timestep.unsqueeze(1).expand(-1, self.hidden_states.shape[1]), kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=self.kv_cache_starts, current_end=self.kv_cache_ends, block_mode=block_mode, block_num=block_num, patched_x_shape=patched_x_shape, block_x=self.block_x, ) return denoised_pred, patched_x_shape