Skip to content

causal_fast_pipeline

LingBot World 2 causal-fast image-to-video pipeline.

Classes

fastvideo.pipelines.basic.lingbotworld2.causal_fast_pipeline.LingBotWorld2CausalFastGenerationStage

LingBotWorld2CausalFastGenerationStage(transformer, scheduler, vae)

Bases: PipelineStage

Prepare LingBot World 2 conditions and run the released causal-fast sampling loop.

Source code in fastvideo/pipelines/basic/lingbotworld2/causal_fast_pipeline.py
def __init__(self, transformer, scheduler, vae) -> None:
    super().__init__()
    self.transformer = transformer
    self.scheduler = scheduler
    self.vae = vae
    self._cross_attn_initialized = False

Methods:

fastvideo.pipelines.basic.lingbotworld2.causal_fast_pipeline.LingBotWorld2CausalFastGenerationStage.forward
forward(batch: ForwardBatch, fastvideo_args: FastVideoArgs) -> ForwardBatch

Execute LingBot World 2 causal-fast generation and store final latents on the batch.

Source code in fastvideo/pipelines/basic/lingbotworld2/causal_fast_pipeline.py
@torch.no_grad()
def forward(self, batch: ForwardBatch, fastvideo_args: FastVideoArgs) -> ForwardBatch:
    """Execute LingBot World 2 causal-fast generation and store final latents on the batch."""
    device = get_local_torch_device()
    cfg = fastvideo_args.pipeline_config.dit_config.arch_config
    chunk_size = int(cfg.chunk_size)
    max_sequence_length = int(batch.max_sequence_length or cfg.text_len)
    action_path = batch.action_path
    if action_path is None:
        raise ValueError("LingBot World 2 causal-fast requires `action_path`.")

    c2ws = np.load(os.path.join(action_path, "poses.npy"))
    len_c2ws = ((len(c2ws) - 1) // 4) * 4 + 1
    frame_num = ((int(batch.num_frames) - 1) // 4) * 4 + 1
    frame_num = min(frame_num, len_c2ws)
    c2ws = c2ws[:frame_num]

    img = self._prepare_image_tensor(batch, device)
    h0, w0 = img.shape[1:]
    aspect_ratio = h0 / w0
    lat_h = round(np.sqrt(cfg.max_area * aspect_ratio) // 8 // cfg.patch_size[1] * cfg.patch_size[1])
    lat_w = round(np.sqrt(cfg.max_area / aspect_ratio) // 8 // cfg.patch_size[2] * cfg.patch_size[2])
    h = lat_h * 8
    w = lat_w * 8
    lat_f = (frame_num - 1) // 4 + 1
    lat_f = int(lat_f - (lat_f % chunk_size))
    frames = (lat_f - 1) * 4 + 1
    batch.height = h
    batch.width = w
    batch.num_frames = frames

    seed = int(batch.seed if batch.seed is not None else 42)
    seed_g = torch.Generator(device=device)
    seed_g.manual_seed(seed)
    noise = torch.randn(16, lat_f, lat_h, lat_w, dtype=torch.float32, generator=seed_g, device=device)

    mask = torch.ones(1, frames, lat_h, lat_w, device=device)
    mask[:, 1:] = 0
    mask = torch.concat([torch.repeat_interleave(mask[:, 0:1], repeats=4, dim=1), mask[:, 1:]], dim=1)
    mask = mask.view(1, mask.shape[1] // 4, 4, lat_h, lat_w).transpose(1, 2)[0]

    self.scheduler.set_timesteps(cfg.num_train_timesteps, shift=cfg.sample_shift)
    timesteps = self.scheduler.timesteps[list(cfg.timesteps_index)].to(device)
    context = self._prompt_context(batch, device)
    c2ws_plucker_emb = self._prepare_camera(
        action_path,
        c2ws,
        h,
        w,
        lat_f,
        lat_h,
        lat_w,
        chunk_size,
        torch.bfloat16,
        device,
    )
    y = self._encode_condition_video(img, h, w, frames, mask, fastvideo_args).to(device=device,
                                                                                 dtype=torch.bfloat16)

    transformer_dtype = torch.bfloat16
    frame_seqlen = int(noise.shape[-2] * noise.shape[-1] // 4)
    kv_size = frame_seqlen * cfg.local_attn_size if cfg.local_attn_size > -1 else frame_seqlen * lat_f
    self_kv_cache = self._initialize_self_kv_cache(1, kv_size, transformer_dtype, device)
    cross_kv_cache = self._initialize_crossattn_cache(1, max_sequence_length, transformer_dtype, device)

    self.transformer = self.transformer.to(device)
    self._cross_attn_initialized = False
    pred_latent_chunks = []
    latents_chunk = noise.split(chunk_size, dim=1)
    condition_chunk = y.split(chunk_size, dim=1)
    c2ws_plucker_emb_chunk = c2ws_plucker_emb.split(chunk_size, dim=2)
    max_seq_len = int(math.ceil(chunk_size * lat_h * lat_w // 4))

    with torch.amp.autocast("cuda", dtype=transformer_dtype):
        for chunk_id, current_latent in enumerate(latents_chunk):
            current_condition = condition_chunk[chunk_id]
            current_c2ws_plucker_emb = c2ws_plucker_emb_chunk[chunk_id]
            dit_cond_dict = {"c2ws_plucker_emb": current_c2ws_plucker_emb.chunk(1, dim=0)}
            kwargs = {
                "context": [context[0]],
                "seq_len": max_seq_len,
                "y": [current_condition],
                "dit_cond_dict": dit_cond_dict,
                "kv_cache": self_kv_cache,
                "crossattn_cache": cross_kv_cache,
                "current_start": chunk_id * chunk_size * frame_seqlen,
                "max_attention_size": kv_size,
                "frame_seqlen": frame_seqlen,
            }
            x0 = current_latent
            for timestep_idx, timestep_value in enumerate(timesteps):
                timestep = torch.stack([timestep_value]).to(device)
                noise_pred = self.transformer(
                    x=[current_latent.to(device)],
                    t=timestep,
                    cross_attn_first_call=not self._cross_attn_initialized,
                    **kwargs,
                )[0]
                self._cross_attn_initialized = True
                x0 = self._convert_flow_pred_to_x0(noise_pred, current_latent, timestep_value)
                if timestep_idx < len(timesteps) - 1:
                    next_timestep = timesteps[timestep_idx + 1].reshape(1)
                    current_latent = self.scheduler.add_noise(
                        x0,
                        torch.randn(x0.shape, generator=seed_g, device=x0.device, dtype=x0.dtype),
                        next_timestep,
                    )
            pred_latent_chunks.append(x0)
            context_timestep = torch.stack([timesteps[-1] * 0.0]).to(device)
            self.transformer(x=[x0], t=context_timestep, cross_attn_first_call=False, **kwargs)

    batch.latents = torch.cat(pred_latent_chunks, dim=1).unsqueeze(0)
    return batch

fastvideo.pipelines.basic.lingbotworld2.causal_fast_pipeline.LingBotWorld2CausalFastPipeline

LingBotWorld2CausalFastPipeline(*args, **kwargs)

Bases: LoRAPipeline, ComposedPipelineBase

FastVideo pipeline for LingBot World 2 14B causal-fast I2V generation.

Source code in fastvideo/pipelines/lora_pipeline.py
def __init__(self, *args, **kwargs) -> None:
    super().__init__(*args, **kwargs)
    self.device = get_local_torch_device()
    self.lora_adapter_paths = {}
    # build list of trainable transformers
    for transformer_name in self.trainable_transformer_names:
        if (transformer_name in self.modules and self.modules[transformer_name] is not None):
            self.trainable_transformer_modules[transformer_name] = (self.modules[transformer_name])
        # check for transformer_2 in case of Wan2.2 MoE or fake_score_transformer_2
        if transformer_name.endswith("_2"):
            raise ValueError(
                f"trainable_transformer_name override in pipelines should not include _2 suffix: {transformer_name}"
            )

        secondary_transformer_name = transformer_name + "_2"
        if (secondary_transformer_name in self.modules and self.modules[secondary_transformer_name] is not None):
            self.trainable_transformer_modules[secondary_transformer_name] = self.modules[
                secondary_transformer_name]

    logger.info(
        "trainable_transformer_modules: %s",
        self.trainable_transformer_modules.keys(),
    )

    for (
            transformer_name,
            transformer_module,
    ) in self.trainable_transformer_modules.items():
        self.exclude_lora_layers[transformer_name] = (transformer_module.config.arch_config.exclude_lora_layers)
    self.lora_target_modules = self.fastvideo_args.lora_target_modules
    self.lora_path = self.fastvideo_args.lora_path
    self.lora_nickname = self.fastvideo_args.lora_nickname
    self.training_mode = self.fastvideo_args.training_mode
    if self.training_mode and getattr(self.fastvideo_args, "lora_training", False):
        assert isinstance(self.fastvideo_args, TrainingArgs)
        if self.fastvideo_args.lora_alpha is None:
            self.fastvideo_args.lora_alpha = self.fastvideo_args.lora_rank
        self.lora_rank = self.fastvideo_args.lora_rank  # type: ignore
        self.lora_alpha = self.fastvideo_args.lora_alpha  # type: ignore
        logger.info(
            "Using LoRA training with rank %d and alpha %d",
            self.lora_rank,
            self.lora_alpha,
        )
        if self.lora_target_modules is None:
            self.lora_target_modules = [
                "q_proj",
                "k_proj",
                "v_proj",
                "o_proj",
                "to_q",
                "to_k",
                "to_v",
                "to_out",
                "to_qkv",
                "to_gate_compress",
            ]
            logger.info(
                "Using default lora_target_modules for all transformers: %s",
                self.lora_target_modules,
            )
        else:
            logger.warning(
                "Using custom lora_target_modules for all transformers, which may not be intended: %s",
                self.lora_target_modules,
            )

        self.convert_to_lora_layers()
    # Inference
    elif not self.training_mode and self.lora_path is not None:
        self.convert_to_lora_layers()
        self.set_lora_adapter(
            self.lora_nickname,  # type: ignore
            self.lora_path,
        )  # type: ignore

Methods:

fastvideo.pipelines.basic.lingbotworld2.causal_fast_pipeline.LingBotWorld2CausalFastPipeline.create_pipeline_stages
create_pipeline_stages(fastvideo_args: FastVideoArgs) -> None

Set up the LingBot World 2 causal-fast pipeline stages.

Source code in fastvideo/pipelines/basic/lingbotworld2/causal_fast_pipeline.py
def create_pipeline_stages(self, fastvideo_args: FastVideoArgs) -> None:
    """Set up the LingBot World 2 causal-fast pipeline stages."""
    self.add_stage(stage_name="input_validation_stage", stage=InputValidationStage())
    self.add_stage(
        stage_name="prompt_encoding_stage",
        stage=LingBotWorld2TextEncodingStage(
            text_encoders=[self.get_module("text_encoder")],
            tokenizers=[self.get_module("tokenizer")],
        ),
    )
    self.add_stage(stage_name="conditioning_stage", stage=ConditioningStage())
    self.add_stage(
        stage_name="lingbotworld2_causal_fast_generation_stage",
        stage=LingBotWorld2CausalFastGenerationStage(
            transformer=self.get_module("transformer"),
            scheduler=self.get_module("scheduler"),
            vae=self.get_module("vae"),
        ),
    )
    self.add_stage(stage_name="decoding_stage", stage=DecodingStage(vae=self.get_module("vae")))

fastvideo.pipelines.basic.lingbotworld2.causal_fast_pipeline.LingBotWorld2TextEncodingStage

LingBotWorld2TextEncodingStage(text_encoders, tokenizers)

Bases: TextEncodingStage

Keep LingBot World 2 T5 attention masks so DiT context matches the source pipeline.

Source code in fastvideo/pipelines/stages/text_encoding.py
def __init__(self, text_encoders, tokenizers) -> None:
    """
    Initialize the prompt encoding stage.

    Args:
        enable_logging: Whether to enable logging for this stage.
        is_secondary: Whether this is a secondary text encoder.
    """
    super().__init__()
    self.tokenizers = tokenizers
    self.text_encoders = text_encoders
    self._last_audio_embeds: list[torch.Tensor] | None = None

Methods:

fastvideo.pipelines.basic.lingbotworld2.causal_fast_pipeline.LingBotWorld2TextEncodingStage.forward
forward(batch: ForwardBatch, fastvideo_args: FastVideoArgs) -> ForwardBatch

Initialize mask storage before running the shared text-encoding stage.

Source code in fastvideo/pipelines/basic/lingbotworld2/causal_fast_pipeline.py
def forward(self, batch: ForwardBatch, fastvideo_args: FastVideoArgs) -> ForwardBatch:
    """Initialize mask storage before running the shared text-encoding stage."""
    if batch.prompt_attention_mask is None:
        batch.prompt_attention_mask = []
    return super().forward(batch, fastvideo_args)

Functions: