Skip to content

ltx2vae

LTX-2 Video VAE implementation

Classes

fastvideo.models.vaes.ltx2vae.CausalConv3d

CausalConv3d(in_channels: int, out_channels: int, kernel_size: int = 3, stride: int | Tuple[int, int, int] = 1, dilation: int = 1, groups: int = 1, bias: bool = True, spatial_padding_mode: PaddingModeType = ZEROS)

Bases: Module

Causal 3D convolution that pads temporally by repeating the first frame.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    in_channels: int,
    out_channels: int,
    kernel_size: int = 3,
    stride: int | Tuple[int, int, int] = 1,
    dilation: int = 1,
    groups: int = 1,
    bias: bool = True,
    spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
) -> None:
    super().__init__()

    self.in_channels = in_channels
    self.out_channels = out_channels

    kernel_size = (kernel_size, kernel_size, kernel_size)
    self.time_kernel_size = kernel_size[0]

    dilation = (dilation, 1, 1)

    height_pad = kernel_size[1] // 2
    width_pad = kernel_size[2] // 2
    padding = (0, height_pad, width_pad)

    self.conv = nn.Conv3d(
        in_channels,
        out_channels,
        kernel_size,
        stride=stride,
        dilation=dilation,
        padding=padding,
        padding_mode=spatial_padding_mode.value,
        groups=groups,
        bias=bias,
    )

fastvideo.models.vaes.ltx2vae.DepthToSpaceUpsample

DepthToSpaceUpsample(dims: int, in_channels: int, stride: Tuple[int, int, int], residual: bool = False, out_channels_reduction_factor: int = 1, spatial_padding_mode: PaddingModeType = ZEROS)

Bases: Module

Upsampling via depth-to-space (pixel shuffle).

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    dims: int,
    in_channels: int,
    stride: Tuple[int, int, int],
    residual: bool = False,
    out_channels_reduction_factor: int = 1,
    spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
):
    super().__init__()
    self.stride = stride
    self.out_channels = math.prod(stride) * in_channels // out_channels_reduction_factor
    self.conv = make_conv_nd(
        dims=dims,
        in_channels=in_channels,
        out_channels=self.out_channels,
        kernel_size=3,
        stride=1,
        causal=True,
        spatial_padding_mode=spatial_padding_mode,
    )
    self.residual = residual
    self.out_channels_reduction_factor = out_channels_reduction_factor

fastvideo.models.vaes.ltx2vae.DimensionIntervals dataclass

DimensionIntervals(starts: List[int], ends: List[int], left_ramps: List[int], right_ramps: List[int])

Intervals which a single dimension of the latent space is split into.

fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder

LTX2CausalVideoAutoencoder(config: dict[str, Any])

Bases: Module

LTX-2 VAE that exposes FastVideo's VAE encode/decode interface. Supports tiled decoding to reduce memory usage for high-resolution videos.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, config: dict[str, Any]):
    super().__init__()
    self.config = config
    self.encoder = VideoEncoderConfigurator.from_config(config)
    self.decoder = VideoDecoderConfigurator.from_config(config)
    self._use_tiling: bool = False
    self._use_channels_last_3d: bool = False

    if _is_env_enabled("FASTVIDEO_LTX2_VAE_CHANNELS_LAST_3D", default="1"):
        self.enable_channels_last_3d()

Methods:

fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder.decode
decode(z: Tensor, timestep: Tensor | None = None, generator: Generator | None = None) -> Tensor

Decode latents to video, using tiling if enabled.

Source code in fastvideo/models/vaes/ltx2vae.py
def decode(
    self,
    z: torch.Tensor,
    timestep: torch.Tensor | None = None,
    generator: torch.Generator | None = None,
) -> torch.Tensor:
    """Decode latents to video, using tiling if enabled."""
    z = self._as_channels_last_3d(z)
    if self._use_tiling:
        # Collect all chunks from tiled decode and concatenate
        chunks = list(self.tiled_decode(z, TilingConfig.default(), timestep, generator))
        return torch.cat(chunks, dim=2)  # Concatenate along temporal dimension
    return self.decoder(z, timestep=timestep, generator=generator)
fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder.disable_channels_last_3d
disable_channels_last_3d() -> None

Restore contiguous layout for 3D VAE convolutions.

Source code in fastvideo/models/vaes/ltx2vae.py
def disable_channels_last_3d(self) -> None:
    """Restore contiguous layout for 3D VAE convolutions."""
    self._use_channels_last_3d = False
    self.encoder.to(memory_format=torch.contiguous_format)
    self.decoder.to(memory_format=torch.contiguous_format)
fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder.disable_tiling
disable_tiling() -> None

Disable tiled decoding.

Source code in fastvideo/models/vaes/ltx2vae.py
def disable_tiling(self) -> None:
    """Disable tiled decoding."""
    self._use_tiling = False
fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder.enable_channels_last_3d
enable_channels_last_3d() -> None

Enable channels-last layout for 3D VAE convolutions.

Source code in fastvideo/models/vaes/ltx2vae.py
def enable_channels_last_3d(self) -> None:
    """Enable channels-last layout for 3D VAE convolutions."""
    self._use_channels_last_3d = True
    self.encoder.to(memory_format=torch.channels_last_3d)
    self.decoder.to(memory_format=torch.channels_last_3d)
fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder.enable_tiling
enable_tiling() -> None

Enable tiled decoding with default configuration.

Source code in fastvideo/models/vaes/ltx2vae.py
def enable_tiling(self) -> None:
    """Enable tiled decoding with default configuration."""
    self._use_tiling = True
fastvideo.models.vaes.ltx2vae.LTX2CausalVideoAutoencoder.tiled_decode
tiled_decode(latent: Tensor, tiling_config: TilingConfig | None = None, timestep: Tensor | None = None, generator: Generator | None = None) -> Iterator[Tensor]

Decode a latent tensor into video frames using tiled processing. Splits the latent tensor into tiles, decodes each tile individually, and yields video chunks as they become available.

Parameters:

Name Type Description Default
latent Tensor

Input latent tensor (B, C, F', H', W').

required
tiling_config TilingConfig | None

Tiling configuration for the latent tensor.

None
timestep Tensor | None

Optional timestep for decoder conditioning.

None
generator Generator | None

Optional random generator for deterministic decoding.

None

Yields:

Type Description
Tensor

Video chunks (B, C, T, H, W) by temporal slices.

Source code in fastvideo/models/vaes/ltx2vae.py
def tiled_decode(
    self,
    latent: torch.Tensor,
    tiling_config: TilingConfig | None = None,
    timestep: torch.Tensor | None = None,
    generator: torch.Generator | None = None,
) -> Iterator[torch.Tensor]:
    """
    Decode a latent tensor into video frames using tiled processing.
    Splits the latent tensor into tiles, decodes each tile individually,
    and yields video chunks as they become available.

    Args:
        latent: Input latent tensor (B, C, F', H', W').
        tiling_config: Tiling configuration for the latent tensor.
        timestep: Optional timestep for decoder conditioning.
        generator: Optional random generator for deterministic decoding.

    Yields:
        Video chunks (B, C, T, H, W) by temporal slices.
    """
    full_video_shape = VideoLatentShape.from_torch_shape(latent.shape).upscale(
        self.TIME_SCALE, self.SPATIAL_SCALE
    )
    tiles = self._prepare_tiles(latent, tiling_config)
    temporal_groups = self._group_tiles_by_temporal_slice(tiles)

    previous_chunk = None
    previous_weights = None
    previous_temporal_slice = None

    for temporal_group_tiles in temporal_groups:
        curr_temporal_slice = temporal_group_tiles[0].out_coords[2]

        temporal_tile_buffer_shape = full_video_shape._replace(
            frames=curr_temporal_slice.stop - curr_temporal_slice.start,
        )

        buffer = torch.zeros(
            temporal_tile_buffer_shape.to_torch_shape(),
            device=latent.device,
            dtype=latent.dtype,
        )

        curr_weights = self._accumulate_temporal_group_into_buffer(
            group_tiles=temporal_group_tiles,
            buffer=buffer,
            latent=latent,
            timestep=timestep,
            generator=generator,
        )

        # Blend with previous temporal chunk if it exists
        if previous_chunk is not None:
            if previous_temporal_slice.stop > curr_temporal_slice.start:
                overlap_len = previous_temporal_slice.stop - curr_temporal_slice.start
                temporal_overlap_slice = slice(curr_temporal_slice.start - previous_temporal_slice.start, None)

                previous_chunk[:, :, temporal_overlap_slice, :, :] += buffer[:, :, slice(0, overlap_len), :, :]
                previous_weights[:, :, temporal_overlap_slice, :, :] += curr_weights[
                    :, :, slice(0, overlap_len), :, :
                ]

                buffer[:, :, slice(0, overlap_len), :, :] = previous_chunk[:, :, temporal_overlap_slice, :, :]
                curr_weights[:, :, slice(0, overlap_len), :, :] = previous_weights[
                    :, :, temporal_overlap_slice, :, :
                ]

            # Yield the non-overlapping part of the previous chunk
            previous_weights = previous_weights.clamp(min=1e-8)
            yield_len = curr_temporal_slice.start - previous_temporal_slice.start
            yield (previous_chunk / previous_weights)[:, :, :yield_len, :, :]

        # Update state for next iteration
        previous_chunk = buffer
        previous_weights = curr_weights
        previous_temporal_slice = curr_temporal_slice

    # Yield any remaining chunk
    if previous_chunk is not None:
        previous_weights = previous_weights.clamp(min=1e-8)
        yield previous_chunk / previous_weights

fastvideo.models.vaes.ltx2vae.LTX2VideoDecoder

LTX2VideoDecoder(config: dict[str, Any])

Bases: Module

LTX-2 Video Decoder wrapper for FastVideo compatibility.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, config: dict[str, Any]):
    super().__init__()
    self.model: VideoDecoder = VideoDecoderConfigurator.from_config(config)

fastvideo.models.vaes.ltx2vae.LTX2VideoEncoder

LTX2VideoEncoder(config: dict[str, Any])

Bases: Module

LTX-2 Video Encoder wrapper for FastVideo compatibility.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, config: dict[str, Any]):
    super().__init__()
    self.model: VideoEncoder = VideoEncoderConfigurator.from_config(config)

fastvideo.models.vaes.ltx2vae.LatentIntervals dataclass

LatentIntervals(original_shape: Size, dimension_intervals: Tuple[DimensionIntervals, ...])

Intervals which the latent tensor of given shape is split into.

fastvideo.models.vaes.ltx2vae.PerChannelStatistics

PerChannelStatistics(latent_channels: int = 128)

Bases: Module

Per-channel statistics for normalizing and denormalizing the latent representation. Statistics are computed over the dataset and stored in the model checkpoint.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, latent_channels: int = 128):
    super().__init__()
    self.register_buffer("std-of-means", torch.ones(latent_channels))
    self.register_buffer("mean-of-means", torch.zeros(latent_channels))
    self.register_buffer("mean-of-stds", torch.ones(latent_channels))
    self.register_buffer("mean-of-stds_over_std-of-means", torch.ones(latent_channels))
    self.register_buffer("channel", torch.arange(latent_channels, dtype=torch.float32))

fastvideo.models.vaes.ltx2vae.PixArtAlphaCombinedTimestepSizeEmbeddings

PixArtAlphaCombinedTimestepSizeEmbeddings(embedding_dim: int, size_emb_dim: int = 0)

Bases: Module

Timestep embeddings for decoder conditioning.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, embedding_dim: int, size_emb_dim: int = 0):
    super().__init__()
    self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=0)
    self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)

fastvideo.models.vaes.ltx2vae.PixelNorm

PixelNorm(dim: int = 1, eps: float = 1e-08)

Bases: Module

Per-pixel (per-location) RMS normalization layer. Normalizes along the channel dimension using root-mean-square.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, dim: int = 1, eps: float = 1e-8) -> None:
    super().__init__()
    self.dim = dim
    self.eps = eps

fastvideo.models.vaes.ltx2vae.ResnetBlock3D

ResnetBlock3D(dims: int, in_channels: int, out_channels: int | None = None, dropout: float = 0.0, groups: int = 32, eps: float = 1e-06, norm_layer: NormLayerType = PIXEL_NORM, inject_noise: bool = False, timestep_conditioning: bool = False, spatial_padding_mode: PaddingModeType = ZEROS)

Bases: Module

A 3D ResNet block with optional timestep conditioning and noise injection.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    dims: int,
    in_channels: int,
    out_channels: int | None = None,
    dropout: float = 0.0,
    groups: int = 32,
    eps: float = 1e-6,
    norm_layer: NormLayerType = NormLayerType.PIXEL_NORM,
    inject_noise: bool = False,
    timestep_conditioning: bool = False,
    spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
):
    super().__init__()
    self.in_channels = in_channels
    out_channels = in_channels if out_channels is None else out_channels
    self.out_channels = out_channels
    self.inject_noise = inject_noise

    if norm_layer == NormLayerType.GROUP_NORM:
        self.norm1 = nn.GroupNorm(num_groups=groups, num_channels=in_channels, eps=eps, affine=True)
    elif norm_layer == NormLayerType.PIXEL_NORM:
        self.norm1 = PixelNorm()

    self.non_linearity = nn.SiLU()

    self.conv1 = make_conv_nd(
        dims,
        in_channels,
        out_channels,
        kernel_size=3,
        stride=1,
        padding=1,
        causal=True,
        spatial_padding_mode=spatial_padding_mode,
    )

    if inject_noise:
        self.per_channel_scale1 = nn.Parameter(torch.zeros((in_channels, 1, 1)))

    if norm_layer == NormLayerType.GROUP_NORM:
        self.norm2 = nn.GroupNorm(num_groups=groups, num_channels=out_channels, eps=eps, affine=True)
    elif norm_layer == NormLayerType.PIXEL_NORM:
        self.norm2 = PixelNorm()

    self.dropout = nn.Dropout(dropout)

    self.conv2 = make_conv_nd(
        dims,
        out_channels,
        out_channels,
        kernel_size=3,
        stride=1,
        padding=1,
        causal=True,
        spatial_padding_mode=spatial_padding_mode,
    )

    if inject_noise:
        self.per_channel_scale2 = nn.Parameter(torch.zeros((in_channels, 1, 1)))

    self.conv_shortcut = (
        make_linear_nd(dims=dims, in_channels=in_channels, out_channels=out_channels)
        if in_channels != out_channels
        else nn.Identity()
    )

    self.norm3 = (
        nn.GroupNorm(num_groups=1, num_channels=in_channels, eps=eps, affine=True)
        if in_channels != out_channels
        else nn.Identity()
    )

    self.timestep_conditioning = timestep_conditioning

    if timestep_conditioning:
        self.scale_shift_table = nn.Parameter(torch.randn(4, in_channels) / in_channels**0.5)

fastvideo.models.vaes.ltx2vae.SpaceToDepthDownsample

SpaceToDepthDownsample(dims: int, in_channels: int, out_channels: int, stride: Tuple[int, int, int], spatial_padding_mode: PaddingModeType = ZEROS)

Bases: Module

Downsampling via space-to-depth with residual connection.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    dims: int,
    in_channels: int,
    out_channels: int,
    stride: Tuple[int, int, int],
    spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
):
    super().__init__()
    self.stride = stride
    self.group_size = in_channels * math.prod(stride) // out_channels
    self.conv = make_conv_nd(
        dims=dims,
        in_channels=in_channels,
        out_channels=out_channels // math.prod(stride),
        kernel_size=3,
        stride=1,
        causal=True,
        spatial_padding_mode=spatial_padding_mode,
    )

fastvideo.models.vaes.ltx2vae.SpatialTilingConfig dataclass

SpatialTilingConfig(tile_size_in_pixels: int, tile_overlap_in_pixels: int = 0)

Configuration for dividing each frame into spatial tiles with optional overlap.

fastvideo.models.vaes.ltx2vae.TemporalTilingConfig dataclass

TemporalTilingConfig(tile_size_in_frames: int, tile_overlap_in_frames: int = 0)

Configuration for dividing a video into temporal tiles (chunks of frames) with optional overlap.

fastvideo.models.vaes.ltx2vae.Tile

Bases: NamedTuple

Represents a single tile.

fastvideo.models.vaes.ltx2vae.TilingConfig dataclass

TilingConfig(spatial_config: SpatialTilingConfig | None = None, temporal_config: TemporalTilingConfig | None = None)

Configuration for splitting video into tiles with optional overlap.

fastvideo.models.vaes.ltx2vae.TimestepEmbedding

TimestepEmbedding(in_channels: int, time_embed_dim: int)

Bases: Module

MLP for timestep embeddings.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, in_channels: int, time_embed_dim: int):
    super().__init__()
    self.linear_1 = nn.Linear(in_channels, time_embed_dim)
    self.act = nn.SiLU()
    self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim)

fastvideo.models.vaes.ltx2vae.Timesteps

Timesteps(num_channels: int, flip_sin_to_cos: bool = True, downscale_freq_shift: float = 0)

Bases: Module

Sinusoidal timestep embeddings.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(self, num_channels: int, flip_sin_to_cos: bool = True, downscale_freq_shift: float = 0):
    super().__init__()
    self.num_channels = num_channels
    self.flip_sin_to_cos = flip_sin_to_cos
    self.downscale_freq_shift = downscale_freq_shift

fastvideo.models.vaes.ltx2vae.UNetMidBlock3D

UNetMidBlock3D(dims: int, in_channels: int, dropout: float = 0.0, num_layers: int = 1, resnet_eps: float = 1e-06, resnet_groups: int = 32, norm_layer: NormLayerType = GROUP_NORM, inject_noise: bool = False, timestep_conditioning: bool = False, spatial_padding_mode: PaddingModeType = ZEROS, attention_head_dim: int | None = None)

Bases: Module

A 3D UNet mid-block with multiple residual blocks.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    dims: int,
    in_channels: int,
    dropout: float = 0.0,
    num_layers: int = 1,
    resnet_eps: float = 1e-6,
    resnet_groups: int = 32,
    norm_layer: NormLayerType = NormLayerType.GROUP_NORM,
    inject_noise: bool = False,
    timestep_conditioning: bool = False,
    spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
    attention_head_dim: int | None = None,  # unused, for compatibility
):
    super().__init__()
    resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)

    self.timestep_conditioning = timestep_conditioning

    if timestep_conditioning:
        self.time_embedder = PixArtAlphaCombinedTimestepSizeEmbeddings(
            embedding_dim=in_channels * 4, size_emb_dim=0
        )

    self.res_blocks = nn.ModuleList(
        [
            ResnetBlock3D(
                dims=dims,
                in_channels=in_channels,
                out_channels=in_channels,
                eps=resnet_eps,
                groups=resnet_groups,
                dropout=dropout,
                norm_layer=norm_layer,
                inject_noise=inject_noise,
                timestep_conditioning=timestep_conditioning,
                spatial_padding_mode=spatial_padding_mode,
            )
            for _ in range(num_layers)
        ]
    )

fastvideo.models.vaes.ltx2vae.VideoDecoder

VideoDecoder(convolution_dimensions: int = 3, in_channels: int = 128, out_channels: int = 3, decoder_blocks: list[tuple[str, int | dict]] = [], patch_size: int = 4, norm_layer: NormLayerType = PIXEL_NORM, causal: bool = False, timestep_conditioning: bool = False, decoder_spatial_padding_mode: PaddingModeType = REFLECT)

Bases: Module

LTX-2 Video Decoder. Decodes latent representation into video frames.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    convolution_dimensions: int = 3,
    in_channels: int = 128,
    out_channels: int = 3,
    decoder_blocks: list[tuple[str, int | dict]] = [],
    patch_size: int = 4,
    norm_layer: NormLayerType = NormLayerType.PIXEL_NORM,
    causal: bool = False,
    timestep_conditioning: bool = False,
    decoder_spatial_padding_mode: PaddingModeType = PaddingModeType.REFLECT,
):
    super().__init__()

    self.patch_size = patch_size
    out_channels = out_channels * patch_size**2
    self.causal = causal
    self.timestep_conditioning = timestep_conditioning
    self._norm_num_groups = self._DEFAULT_NORM_NUM_GROUPS

    self.per_channel_statistics = PerChannelStatistics(latent_channels=in_channels)

    self.decode_noise_scale = 0.025
    self.decode_timestep = 0.05

    feature_channels = in_channels
    for block_name, block_params in list(reversed(decoder_blocks)):
        block_config = block_params if isinstance(block_params, dict) else {}
        if block_name == "res_x_y":
            feature_channels = feature_channels * block_config.get("multiplier", 2)
        if block_name == "compress_all":
            feature_channels = feature_channels * block_config.get("multiplier", 1)
        if block_name == "compress_space":
            feature_channels = feature_channels * block_config.get("multiplier", 1)
        if block_name == "compress_time":
            feature_channels = feature_channels * block_config.get("multiplier", 1)

    self.conv_in = make_conv_nd(
        dims=convolution_dimensions,
        in_channels=in_channels,
        out_channels=feature_channels,
        kernel_size=3,
        stride=1,
        padding=1,
        causal=True,
        spatial_padding_mode=decoder_spatial_padding_mode,
    )

    self.up_blocks = nn.ModuleList([])

    for block_name, block_params in list(reversed(decoder_blocks)):
        block_config = {"num_layers": block_params} if isinstance(block_params, int) else block_params

        block, feature_channels = _make_decoder_block(
            block_name=block_name,
            block_config=block_config,
            in_channels=feature_channels,
            convolution_dimensions=convolution_dimensions,
            norm_layer=norm_layer,
            timestep_conditioning=timestep_conditioning,
            norm_num_groups=self._norm_num_groups,
            spatial_padding_mode=decoder_spatial_padding_mode,
        )

        self.up_blocks.append(block)

    if norm_layer == NormLayerType.GROUP_NORM:
        self.conv_norm_out = nn.GroupNorm(
            num_channels=feature_channels, num_groups=self._norm_num_groups, eps=1e-6
        )
    elif norm_layer == NormLayerType.PIXEL_NORM:
        self.conv_norm_out = PixelNorm()

    self.conv_act = nn.SiLU()
    self.conv_out = make_conv_nd(
        dims=convolution_dimensions,
        in_channels=feature_channels,
        out_channels=out_channels,
        kernel_size=3,
        padding=1,
        causal=True,
        spatial_padding_mode=decoder_spatial_padding_mode,
    )

    if timestep_conditioning:
        self.timestep_scale_multiplier = nn.Parameter(torch.tensor(1000.0))
        self.last_time_embedder = PixArtAlphaCombinedTimestepSizeEmbeddings(
            embedding_dim=feature_channels * 2, size_emb_dim=0
        )
        self.last_scale_shift_table = nn.Parameter(torch.empty(2, feature_channels))

fastvideo.models.vaes.ltx2vae.VideoDecoderConfigurator

Configurator for creating a video VAE Decoder from a configuration dictionary.

fastvideo.models.vaes.ltx2vae.VideoEncoder

VideoEncoder(convolution_dimensions: int = 3, in_channels: int = 3, out_channels: int = 128, encoder_blocks: list[tuple[str, int]] | list[tuple[str, dict[str, Any]]] = [], patch_size: int = 4, norm_layer: NormLayerType = PIXEL_NORM, latent_log_var: LogVarianceType = UNIFORM, encoder_spatial_padding_mode: PaddingModeType = ZEROS)

Bases: Module

LTX-2 Video Encoder. Encodes video frames into a latent representation.

Source code in fastvideo/models/vaes/ltx2vae.py
def __init__(
    self,
    convolution_dimensions: int = 3,
    in_channels: int = 3,
    out_channels: int = 128,
    encoder_blocks: list[tuple[str, int]] | list[tuple[str, dict[str, Any]]] = [],
    patch_size: int = 4,
    norm_layer: NormLayerType = NormLayerType.PIXEL_NORM,
    latent_log_var: LogVarianceType = LogVarianceType.UNIFORM,
    encoder_spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
):
    super().__init__()

    self.patch_size = patch_size
    self.norm_layer = norm_layer
    self.latent_channels = out_channels
    self.latent_log_var = latent_log_var
    self._norm_num_groups = self._DEFAULT_NORM_NUM_GROUPS

    self.per_channel_statistics = PerChannelStatistics(latent_channels=out_channels)

    in_channels = in_channels * patch_size**2
    feature_channels = out_channels

    self.conv_in = make_conv_nd(
        dims=convolution_dimensions,
        in_channels=in_channels,
        out_channels=feature_channels,
        kernel_size=3,
        stride=1,
        padding=1,
        causal=True,
        spatial_padding_mode=encoder_spatial_padding_mode,
    )

    self.down_blocks = nn.ModuleList([])

    for block_name, block_params in encoder_blocks:
        block_config = {"num_layers": block_params} if isinstance(block_params, int) else block_params

        block, feature_channels = _make_encoder_block(
            block_name=block_name,
            block_config=block_config,
            in_channels=feature_channels,
            convolution_dimensions=convolution_dimensions,
            norm_layer=norm_layer,
            norm_num_groups=self._norm_num_groups,
            spatial_padding_mode=encoder_spatial_padding_mode,
        )

        self.down_blocks.append(block)

    if norm_layer == NormLayerType.GROUP_NORM:
        self.conv_norm_out = nn.GroupNorm(
            num_channels=feature_channels, num_groups=self._norm_num_groups, eps=1e-6
        )
    elif norm_layer == NormLayerType.PIXEL_NORM:
        self.conv_norm_out = PixelNorm()

    self.conv_act = nn.SiLU()

    conv_out_channels = out_channels
    if latent_log_var == LogVarianceType.PER_CHANNEL:
        conv_out_channels *= 2
    elif latent_log_var in {LogVarianceType.UNIFORM, LogVarianceType.CONSTANT}:
        conv_out_channels += 1
    elif latent_log_var != LogVarianceType.NONE:
        raise ValueError(f"Invalid latent_log_var: {latent_log_var}")

    self.conv_out = make_conv_nd(
        dims=convolution_dimensions,
        in_channels=feature_channels,
        out_channels=conv_out_channels,
        kernel_size=3,
        padding=1,
        causal=True,
        spatial_padding_mode=encoder_spatial_padding_mode,
    )

fastvideo.models.vaes.ltx2vae.VideoEncoderConfigurator

Configurator for creating a video VAE Encoder from a configuration dictionary.

fastvideo.models.vaes.ltx2vae.VideoLatentShape

Bases: NamedTuple

Shape of the tensor representing video in VAE latent space.

Functions:

fastvideo.models.vaes.ltx2vae.compute_trapezoidal_mask_1d

compute_trapezoidal_mask_1d(length: int, ramp_left: int, ramp_right: int, left_starts_from_0: bool = False) -> Tensor

Generate a 1D trapezoidal blending mask with linear ramps.

Source code in fastvideo/models/vaes/ltx2vae.py
def compute_trapezoidal_mask_1d(
    length: int,
    ramp_left: int,
    ramp_right: int,
    left_starts_from_0: bool = False,
) -> torch.Tensor:
    """Generate a 1D trapezoidal blending mask with linear ramps."""
    if length <= 0:
        raise ValueError("Mask length must be positive.")

    ramp_left = max(0, min(ramp_left, length))
    ramp_right = max(0, min(ramp_right, length))

    mask = torch.ones(length)

    if ramp_left > 0:
        interval_length = ramp_left + 1 if left_starts_from_0 else ramp_left + 2
        fade_in = torch.linspace(0.0, 1.0, interval_length)[:-1]
        if not left_starts_from_0:
            fade_in = fade_in[1:]
        mask[:ramp_left] *= fade_in

    if ramp_right > 0:
        fade_out = torch.linspace(1.0, 0.0, steps=ramp_right + 2)[1:-1]
        mask[-ramp_right:] *= fade_out

    return mask.clamp_(0, 1)

fastvideo.models.vaes.ltx2vae.make_conv_nd

make_conv_nd(dims: int, in_channels: int, out_channels: int, kernel_size: int, stride: int | Tuple[int, int, int] = 1, padding: int = 0, dilation: int = 1, groups: int = 1, bias: bool = True, causal: bool = False, spatial_padding_mode: PaddingModeType = ZEROS) -> Module

Create a convolution layer (2D or 3D, causal or not).

Source code in fastvideo/models/vaes/ltx2vae.py
def make_conv_nd(
    dims: int,
    in_channels: int,
    out_channels: int,
    kernel_size: int,
    stride: int | Tuple[int, int, int] = 1,
    padding: int = 0,
    dilation: int = 1,
    groups: int = 1,
    bias: bool = True,
    causal: bool = False,
    spatial_padding_mode: PaddingModeType = PaddingModeType.ZEROS,
) -> nn.Module:
    """Create a convolution layer (2D or 3D, causal or not)."""
    if dims == 2:
        return nn.Conv2d(
            in_channels=in_channels,
            out_channels=out_channels,
            kernel_size=kernel_size,
            stride=stride,
            padding=padding,
            dilation=dilation,
            groups=groups,
            bias=bias,
            padding_mode=spatial_padding_mode.value,
        )
    elif dims == 3:
        if causal:
            return CausalConv3d(
                in_channels=in_channels,
                out_channels=out_channels,
                kernel_size=kernel_size,
                stride=stride,
                dilation=dilation,
                groups=groups,
                bias=bias,
                spatial_padding_mode=spatial_padding_mode,
            )
        return nn.Conv3d(
            in_channels=in_channels,
            out_channels=out_channels,
            kernel_size=kernel_size,
            stride=stride,
            padding=padding,
            dilation=dilation,
            groups=groups,
            bias=bias,
            padding_mode=spatial_padding_mode.value,
        )
    else:
        raise ValueError(f"unsupported dimensions: {dims}")

fastvideo.models.vaes.ltx2vae.make_linear_nd

make_linear_nd(dims: int, in_channels: int, out_channels: int, bias: bool = True) -> Module

Create a 1x1 convolution (pointwise linear).

Source code in fastvideo/models/vaes/ltx2vae.py
def make_linear_nd(
    dims: int,
    in_channels: int,
    out_channels: int,
    bias: bool = True,
) -> nn.Module:
    """Create a 1x1 convolution (pointwise linear)."""
    if dims == 2:
        return nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, bias=bias)
    elif dims == 3:
        return nn.Conv3d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, bias=bias)
    else:
        raise ValueError(f"unsupported dimensions: {dims}")

fastvideo.models.vaes.ltx2vae.patchify

patchify(x: Tensor, patch_size_hw: int, patch_size_t: int = 1) -> Tensor

Rearrange spatial dimensions into channels (space-to-depth).

Parameters:

Name Type Description Default
x Tensor

Input tensor (4D or 5D)

required
patch_size_hw int

Spatial patch size for height and width.

required
patch_size_t int

Temporal patch size. Default=1 (no temporal patching).

1

For 5D: (B, C, F, H, W) -> (B, Cpatch_size_hw^2patch_size_t, F/patch_size_t, H/patch_size_hw, W/patch_size_hw)

Source code in fastvideo/models/vaes/ltx2vae.py
def patchify(x: torch.Tensor, patch_size_hw: int, patch_size_t: int = 1) -> torch.Tensor:
    """
    Rearrange spatial dimensions into channels (space-to-depth).

    Args:
        x: Input tensor (4D or 5D)
        patch_size_hw: Spatial patch size for height and width.
        patch_size_t: Temporal patch size. Default=1 (no temporal patching).

    For 5D: (B, C, F, H, W) -> (B, C*patch_size_hw^2*patch_size_t, F/patch_size_t, H/patch_size_hw, W/patch_size_hw)
    """
    if patch_size_hw == 1 and patch_size_t == 1:
        return x
    if x.dim() == 4:
        x = rearrange(x, "b c (h q) (w r) -> b (c r q) h w", q=patch_size_hw, r=patch_size_hw)
    elif x.dim() == 5:
        x = rearrange(
            x,
            "b c (f p) (h q) (w r) -> b (c p r q) f h w",
            p=patch_size_t,
            q=patch_size_hw,
            r=patch_size_hw,
        )
    else:
        raise ValueError(f"Invalid input shape: {x.shape}")
    return x

fastvideo.models.vaes.ltx2vae.unpatchify

unpatchify(x: Tensor, patch_size_hw: int, patch_size_t: int = 1) -> Tensor

Rearrange channels back into spatial dimensions (depth-to-space).

Parameters:

Name Type Description Default
x Tensor

Input tensor (4D or 5D)

required
patch_size_hw int

Spatial patch size for height and width.

required
patch_size_t int

Temporal patch size. Default=1 (no temporal expansion).

1

For 5D: (B, Cpatch_size_hw^2patch_size_t, F, H, W) -> (B, C, Fpatch_size_t, Hpatch_size_hw, W*patch_size_hw)

Source code in fastvideo/models/vaes/ltx2vae.py
def unpatchify(x: torch.Tensor, patch_size_hw: int, patch_size_t: int = 1) -> torch.Tensor:
    """
    Rearrange channels back into spatial dimensions (depth-to-space).

    Args:
        x: Input tensor (4D or 5D)
        patch_size_hw: Spatial patch size for height and width.
        patch_size_t: Temporal patch size. Default=1 (no temporal expansion).

    For 5D: (B, C*patch_size_hw^2*patch_size_t, F, H, W) -> (B, C, F*patch_size_t, H*patch_size_hw, W*patch_size_hw)
    """
    if patch_size_hw == 1 and patch_size_t == 1:
        return x

    if x.dim() == 4:
        x = rearrange(x, "b (c r q) h w -> b c (h q) (w r)", q=patch_size_hw, r=patch_size_hw)
    elif x.dim() == 5:
        x = rearrange(
            x,
            "b (c p r q) f h w -> b c (f p) (h q) (w r)",
            p=patch_size_t,
            q=patch_size_hw,
            r=patch_size_hw,
        )
    return x