Spaces:
Running on Zero
Running on Zero
| # Copyright 2025 The Kandinsky Team and The HuggingFace Team. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import math | |
| from typing import Dict, Optional, Tuple, Union | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from ...configuration_utils import ConfigMixin, register_to_config | |
| from ...loaders.single_file_model import FromOriginalModelMixin | |
| from ...utils import logging | |
| from ...utils.accelerate_utils import apply_forward_hook | |
| from ..modeling_outputs import AutoencoderKLOutput | |
| from ..modeling_utils import ModelMixin | |
| from .vae import AutoencoderMixin, DecoderOutput, DiagonalGaussianDistribution | |
| logger = logging.get_logger(__name__) # pylint: disable=invalid-name | |
| def nonlinearity(x: torch.Tensor) -> torch.Tensor: | |
| return F.silu(x) | |
| # ============================================================================= | |
| # Base layers | |
| # ============================================================================= | |
| class KVAESafeConv3d(nn.Conv3d): | |
| r""" | |
| A 3D convolution layer that splits the input tensor into smaller parts to avoid OOM. | |
| """ | |
| def forward(self, input: torch.Tensor, write_to: torch.Tensor = None) -> torch.Tensor: | |
| memory_count = input.numel() * input.element_size() / (10**9) | |
| if memory_count > 3: | |
| kernel_size = self.kernel_size[0] | |
| part_num = math.ceil(memory_count / 2) | |
| input_chunks = torch.chunk(input, part_num, dim=2) | |
| if write_to is None: | |
| output = [] | |
| for i, chunk in enumerate(input_chunks): | |
| if i == 0 or kernel_size == 1: | |
| z = torch.clone(chunk) | |
| else: | |
| z = torch.cat([z[:, :, -kernel_size + 1 :], chunk], dim=2) | |
| output.append(super().forward(z)) | |
| return torch.cat(output, dim=2) | |
| else: | |
| time_offset = 0 | |
| for i, chunk in enumerate(input_chunks): | |
| if i == 0 or kernel_size == 1: | |
| z = torch.clone(chunk) | |
| else: | |
| z = torch.cat([z[:, :, -kernel_size + 1 :], chunk], dim=2) | |
| z_time = z.size(2) - (kernel_size - 1) | |
| write_to[:, :, time_offset : time_offset + z_time] = super().forward(z) | |
| time_offset += z_time | |
| return write_to | |
| else: | |
| if write_to is None: | |
| return super().forward(input) | |
| else: | |
| write_to[...] = super().forward(input) | |
| return write_to | |
| class KVAECausalConv3d(nn.Module): | |
| r""" | |
| A 3D causal convolution layer. | |
| """ | |
| def __init__( | |
| self, | |
| chan_in: int, | |
| chan_out: int, | |
| kernel_size: Union[int, Tuple[int, int, int]], | |
| stride: Tuple[int, int, int] = (1, 1, 1), | |
| dilation: Tuple[int, int, int] = (1, 1, 1), | |
| **kwargs, | |
| ): | |
| super().__init__() | |
| if isinstance(kernel_size, int): | |
| kernel_size = (kernel_size, kernel_size, kernel_size) | |
| time_kernel_size, height_kernel_size, width_kernel_size = kernel_size | |
| self.height_pad = height_kernel_size // 2 | |
| self.width_pad = width_kernel_size // 2 | |
| self.time_pad = time_kernel_size - 1 | |
| self.time_kernel_size = time_kernel_size | |
| self.stride = stride | |
| self.conv = KVAESafeConv3d(chan_in, chan_out, kernel_size, stride=stride, dilation=dilation, **kwargs) | |
| def forward(self, input: torch.Tensor) -> torch.Tensor: | |
| padding_3d = (self.width_pad, self.width_pad, self.height_pad, self.height_pad, self.time_pad, 0) | |
| input_padded = F.pad(input, padding_3d, mode="replicate") | |
| return self.conv(input_padded) | |
| class KVAECachedCausalConv3d(nn.Module): | |
| r""" | |
| A 3D causal convolution layer with caching for temporal processing. | |
| """ | |
| def __init__( | |
| self, | |
| chan_in: int, | |
| chan_out: int, | |
| kernel_size: Union[int, Tuple[int, int, int]], | |
| stride: Tuple[int, int, int] = (1, 1, 1), | |
| dilation: Tuple[int, int, int] = (1, 1, 1), | |
| **kwargs, | |
| ): | |
| super().__init__() | |
| if isinstance(kernel_size, int): | |
| kernel_size = (kernel_size, kernel_size, kernel_size) | |
| time_kernel_size, height_kernel_size, width_kernel_size = kernel_size | |
| self.height_pad = height_kernel_size // 2 | |
| self.width_pad = width_kernel_size // 2 | |
| self.time_pad = time_kernel_size - 1 | |
| self.time_kernel_size = time_kernel_size | |
| self.stride = stride | |
| self.conv = KVAESafeConv3d(chan_in, chan_out, kernel_size, stride=stride, dilation=dilation, **kwargs) | |
| def forward(self, input: torch.Tensor, cache: Dict) -> torch.Tensor: | |
| t_stride = self.stride[0] | |
| padding_3d = (self.height_pad, self.height_pad, self.width_pad, self.width_pad, 0, 0) | |
| input_parallel = F.pad(input, padding_3d, mode="replicate") | |
| if cache["padding"] is None: | |
| first_frame = input_parallel[:, :, :1] | |
| time_pad_shape = list(first_frame.shape) | |
| time_pad_shape[2] = self.time_pad | |
| padding = first_frame.expand(time_pad_shape) | |
| else: | |
| padding = cache["padding"] | |
| out_size = list(input.shape) | |
| out_size[1] = self.conv.out_channels | |
| if t_stride == 2: | |
| out_size[2] = (input.size(2) + 1) // 2 | |
| output = torch.empty(tuple(out_size), dtype=input.dtype, device=input.device) | |
| offset_out = math.ceil(padding.size(2) / t_stride) | |
| offset_in = offset_out * t_stride - padding.size(2) | |
| if offset_out > 0: | |
| padding_poisoned = torch.cat( | |
| [padding, input_parallel[:, :, : offset_in + self.time_kernel_size - t_stride]], dim=2 | |
| ) | |
| output[:, :, :offset_out] = self.conv(padding_poisoned) | |
| if offset_out < output.size(2): | |
| output[:, :, offset_out:] = self.conv(input_parallel[:, :, offset_in:]) | |
| pad_offset = ( | |
| offset_in | |
| + t_stride * math.trunc((input_parallel.size(2) - offset_in - self.time_kernel_size) / t_stride) | |
| + t_stride | |
| ) | |
| cache["padding"] = torch.clone(input_parallel[:, :, pad_offset:]) | |
| return output | |
| class KVAECachedGroupNorm(nn.Module): | |
| r""" | |
| GroupNorm with caching support for temporal processing. | |
| """ | |
| def __init__(self, in_channels: int): | |
| super().__init__() | |
| self.norm_layer = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True) | |
| def forward(self, x: torch.Tensor, cache: Dict = None) -> torch.Tensor: | |
| out = self.norm_layer(x) | |
| if cache is not None and cache.get("mean") is None and cache.get("var") is None: | |
| cache["mean"] = 1 | |
| cache["var"] = 1 | |
| return out | |
| # ============================================================================= | |
| # Cached layers | |
| # ============================================================================= | |
| class KVAECachedSpatialNorm3D(nn.Module): | |
| r""" | |
| Spatially conditioned normalization for decoder with caching. | |
| """ | |
| def __init__( | |
| self, | |
| f_channels: int, | |
| zq_channels: int, | |
| add_conv: bool = False, | |
| ): | |
| super().__init__() | |
| self.norm_layer = KVAECachedGroupNorm(f_channels) | |
| self.add_conv = add_conv | |
| if add_conv: | |
| self.conv = KVAECachedCausalConv3d(chan_in=zq_channels, chan_out=zq_channels, kernel_size=3) | |
| self.conv_y = KVAESafeConv3d(zq_channels, f_channels, kernel_size=1) | |
| self.conv_b = KVAESafeConv3d(zq_channels, f_channels, kernel_size=1) | |
| def forward(self, f: torch.Tensor, zq: torch.Tensor, cache: Dict) -> torch.Tensor: | |
| if cache["norm"].get("mean") is None and cache["norm"].get("var") is None: | |
| f_first, f_rest = f[:, :, :1], f[:, :, 1:] | |
| f_first_size, f_rest_size = f_first.shape[-3:], f_rest.shape[-3:] | |
| zq_first, zq_rest = zq[:, :, :1], zq[:, :, 1:] | |
| zq_first = F.interpolate(zq_first, size=f_first_size, mode="nearest") | |
| if zq.size(2) > 1: | |
| zq_rest_splits = torch.split(zq_rest, 32, dim=1) | |
| interpolated_splits = [ | |
| F.interpolate(split, size=f_rest_size, mode="nearest") for split in zq_rest_splits | |
| ] | |
| zq_rest = torch.cat(interpolated_splits, dim=1) | |
| zq = torch.cat([zq_first, zq_rest], dim=2) | |
| else: | |
| zq = zq_first | |
| else: | |
| f_size = f.shape[-3:] | |
| zq_splits = torch.split(zq, 32, dim=1) | |
| interpolated_splits = [F.interpolate(split, size=f_size, mode="nearest") for split in zq_splits] | |
| zq = torch.cat(interpolated_splits, dim=1) | |
| if self.add_conv: | |
| zq = self.conv(zq, cache["add_conv"]) | |
| norm_f = self.norm_layer(f, cache["norm"]) | |
| norm_f = norm_f * self.conv_y(zq) | |
| norm_f = norm_f + self.conv_b(zq) | |
| return norm_f | |
| class KVAECachedResnetBlock3D(nn.Module): | |
| r""" | |
| A 3D ResNet block with caching. | |
| """ | |
| def __init__( | |
| self, | |
| in_channels: int, | |
| out_channels: Optional[int] = None, | |
| conv_shortcut: bool = False, | |
| dropout: float = 0.0, | |
| temb_channels: int = 0, | |
| zq_ch: Optional[int] = None, | |
| add_conv: bool = False, | |
| gather_norm: bool = False, | |
| ): | |
| 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.use_conv_shortcut = conv_shortcut | |
| if zq_ch is None: | |
| self.norm1 = KVAECachedGroupNorm(in_channels) | |
| else: | |
| self.norm1 = KVAECachedSpatialNorm3D(in_channels, zq_ch, add_conv=add_conv) | |
| self.conv1 = KVAECachedCausalConv3d(chan_in=in_channels, chan_out=out_channels, kernel_size=3) | |
| if temb_channels > 0: | |
| self.temb_proj = nn.Linear(temb_channels, out_channels) | |
| if zq_ch is None: | |
| self.norm2 = KVAECachedGroupNorm(out_channels) | |
| else: | |
| self.norm2 = KVAECachedSpatialNorm3D(out_channels, zq_ch, add_conv=add_conv) | |
| self.conv2 = KVAECachedCausalConv3d(chan_in=out_channels, chan_out=out_channels, kernel_size=3) | |
| if self.in_channels != self.out_channels: | |
| if self.use_conv_shortcut: | |
| self.conv_shortcut = KVAECachedCausalConv3d(chan_in=in_channels, chan_out=out_channels, kernel_size=3) | |
| else: | |
| self.nin_shortcut = KVAESafeConv3d(in_channels, out_channels, kernel_size=1, stride=1, padding=0) | |
| def forward(self, x: torch.Tensor, temb: torch.Tensor, layer_cache: Dict, zq: torch.Tensor = None) -> torch.Tensor: | |
| h = x | |
| if zq is None: | |
| # Encoder path - norm takes cache | |
| h = self.norm1(h, cache=layer_cache["norm1"]) | |
| else: | |
| # Decoder path - spatial norm takes zq and cache | |
| h = self.norm1(h, zq, cache=layer_cache["norm1"]) | |
| h = F.silu(h) | |
| h = self.conv1(h, cache=layer_cache["conv1"]) | |
| if temb is not None: | |
| h = h + self.temb_proj(nonlinearity(temb))[:, :, None, None, None] | |
| if zq is None: | |
| h = self.norm2(h, cache=layer_cache["norm2"]) | |
| else: | |
| h = self.norm2(h, zq, cache=layer_cache["norm2"]) | |
| h = F.silu(h) | |
| h = self.conv2(h, cache=layer_cache["conv2"]) | |
| if self.in_channels != self.out_channels: | |
| if self.use_conv_shortcut: | |
| x = self.conv_shortcut(x, cache=layer_cache["conv_shortcut"]) | |
| else: | |
| x = self.nin_shortcut(x) | |
| return x + h | |
| class KVAECachedPXSDownsample(nn.Module): | |
| r""" | |
| A 3D downsampling layer using PixelUnshuffle with caching. | |
| """ | |
| def __init__(self, in_channels: int, compress_time: bool, factor: int = 2): | |
| super().__init__() | |
| self.temporal_compress = compress_time | |
| self.factor = factor | |
| self.unshuffle = nn.PixelUnshuffle(self.factor) | |
| self.s_pool = nn.AvgPool3d((1, 2, 2), (1, 2, 2)) | |
| self.spatial_conv = KVAESafeConv3d( | |
| in_channels, | |
| in_channels, | |
| kernel_size=(1, 3, 3), | |
| stride=(1, 2, 2), | |
| padding=(0, 1, 1), | |
| padding_mode="reflect", | |
| ) | |
| if self.temporal_compress: | |
| self.temporal_conv = KVAECachedCausalConv3d( | |
| in_channels, in_channels, kernel_size=(3, 1, 1), stride=(2, 1, 1), dilation=(1, 1, 1) | |
| ) | |
| self.linear = nn.Conv3d(in_channels, in_channels, kernel_size=1, stride=1) | |
| def spatial_downsample(self, input: torch.Tensor) -> torch.Tensor: | |
| b, c, t, h, w = input.shape | |
| pxs_input = input.permute(0, 2, 1, 3, 4).reshape(b * t, c, h, w) | |
| # pxs_input = rearrange(input, 'b c t h w -> (b t) c h w') | |
| pxs_interm = self.unshuffle(pxs_input) | |
| b_it, c_it, h_it, w_it = pxs_interm.shape | |
| pxs_interm_view = pxs_interm.view(b_it, c_it // self.factor**2, self.factor**2, h_it, w_it) | |
| pxs_out = torch.mean(pxs_interm_view, dim=2) | |
| pxs_out = pxs_out.view(b, t, -1, h_it, w_it).permute(0, 2, 1, 3, 4) | |
| # pxs_out = rearrange(pxs_out, '(b t) c h w -> b c t h w', t=input.size(2)) | |
| conv_out = self.spatial_conv(input) | |
| return conv_out + pxs_out | |
| def temporal_downsample(self, input: torch.Tensor, cache: list) -> torch.Tensor: | |
| b, c, t, h, w = input.shape | |
| permuted = input.permute(0, 3, 4, 1, 2).reshape(b * h * w, c, t) | |
| if cache[0]["padding"] is None: | |
| first, rest = permuted[..., :1], permuted[..., 1:] | |
| if rest.size(-1) > 0: | |
| rest_interp = F.avg_pool1d(rest, kernel_size=2, stride=2) | |
| full_interp = torch.cat([first, rest_interp], dim=-1) | |
| else: | |
| full_interp = first | |
| else: | |
| rest = permuted | |
| if rest.size(-1) > 0: | |
| full_interp = F.avg_pool1d(rest, kernel_size=2, stride=2) | |
| t_new = full_interp.size(-1) | |
| full_interp = full_interp.view(b, h, w, c, t_new).permute(0, 3, 4, 1, 2) | |
| conv_out = self.temporal_conv(input, cache[0]) | |
| return conv_out + full_interp | |
| def forward(self, x: torch.Tensor, cache: list) -> torch.Tensor: | |
| out = self.spatial_downsample(x) | |
| if self.temporal_compress: | |
| out = self.temporal_downsample(out, cache=cache) | |
| return self.linear(out) | |
| class KVAECachedPXSUpsample(nn.Module): | |
| r""" | |
| A 3D upsampling layer using PixelShuffle with caching. | |
| """ | |
| def __init__(self, in_channels: int, compress_time: bool, factor: int = 2): | |
| super().__init__() | |
| self.temporal_compress = compress_time | |
| self.factor = factor | |
| self.shuffle = nn.PixelShuffle(self.factor) | |
| self.spatial_conv = KVAESafeConv3d( | |
| in_channels, | |
| in_channels, | |
| kernel_size=(1, 3, 3), | |
| stride=(1, 1, 1), | |
| padding=(0, 1, 1), | |
| padding_mode="reflect", | |
| ) | |
| if self.temporal_compress: | |
| self.temporal_conv = KVAECachedCausalConv3d( | |
| in_channels, in_channels, kernel_size=(3, 1, 1), stride=(1, 1, 1), dilation=(1, 1, 1) | |
| ) | |
| self.linear = KVAESafeConv3d(in_channels, in_channels, kernel_size=1, stride=1) | |
| def spatial_upsample(self, input: torch.Tensor) -> torch.Tensor: | |
| b, c, t, h, w = input.shape | |
| input_view = input.permute(0, 2, 1, 3, 4).reshape(b, t * c, h, w) | |
| input_interp = F.interpolate(input_view, scale_factor=2, mode="nearest") | |
| input_interp = input_interp.view(b, t, c, 2 * h, 2 * w).permute(0, 2, 1, 3, 4) | |
| out = self.spatial_conv(input_interp) | |
| return input_interp + out | |
| def temporal_upsample(self, input: torch.Tensor, cache: Dict) -> torch.Tensor: | |
| time_factor = 1.0 + 1.0 * (input.size(2) > 1) | |
| if isinstance(time_factor, torch.Tensor): | |
| time_factor = time_factor.item() | |
| repeated = input.repeat_interleave(int(time_factor), dim=2) | |
| if cache["padding"] is None: | |
| tail = repeated[..., int(time_factor - 1) :, :, :] | |
| else: | |
| tail = repeated | |
| conv_out = self.temporal_conv(tail, cache) | |
| return conv_out + tail | |
| def forward(self, x: torch.Tensor, cache: Dict) -> torch.Tensor: | |
| if self.temporal_compress: | |
| x = self.temporal_upsample(x, cache) | |
| s_out = self.spatial_upsample(x) | |
| to = torch.empty_like(s_out) | |
| lin_out = self.linear(s_out, write_to=to) | |
| return lin_out | |
| # ============================================================================= | |
| # Cached Encoder/Decoder | |
| # ============================================================================= | |
| class KVAECachedEncoder3D(nn.Module): | |
| r""" | |
| Cached 3D Encoder for KVAE. | |
| """ | |
| def __init__( | |
| self, | |
| ch: int = 128, | |
| ch_mult: Tuple[int, ...] = (1, 2, 4, 8), | |
| num_res_blocks: int = 2, | |
| dropout: float = 0.0, | |
| in_channels: int = 3, | |
| z_channels: int = 16, | |
| double_z: bool = True, | |
| temporal_compress_times: int = 4, | |
| ): | |
| super().__init__() | |
| self.ch = ch | |
| self.temb_ch = 0 | |
| self.num_resolutions = len(ch_mult) | |
| self.num_res_blocks = num_res_blocks | |
| self.in_channels = in_channels | |
| self.temporal_compress_level = int(np.log2(temporal_compress_times)) | |
| self.conv_in = KVAECachedCausalConv3d(chan_in=in_channels, chan_out=self.ch, kernel_size=3) | |
| in_ch_mult = (1,) + tuple(ch_mult) | |
| self.down = nn.ModuleList() | |
| block_in = ch | |
| for i_level in range(self.num_resolutions): | |
| block = nn.ModuleList() | |
| attn = nn.ModuleList() | |
| block_in = ch * in_ch_mult[i_level] | |
| block_out = ch * ch_mult[i_level] | |
| for i_block in range(self.num_res_blocks): | |
| block.append( | |
| KVAECachedResnetBlock3D( | |
| in_channels=block_in, | |
| out_channels=block_out, | |
| dropout=dropout, | |
| temb_channels=self.temb_ch, | |
| ) | |
| ) | |
| block_in = block_out | |
| down = nn.Module() | |
| down.block = block | |
| down.attn = attn | |
| if i_level != self.num_resolutions - 1: | |
| if i_level < self.temporal_compress_level: | |
| down.downsample = KVAECachedPXSDownsample(block_in, compress_time=True) | |
| else: | |
| down.downsample = KVAECachedPXSDownsample(block_in, compress_time=False) | |
| self.down.append(down) | |
| self.mid = nn.Module() | |
| self.mid.block_1 = KVAECachedResnetBlock3D( | |
| in_channels=block_in, out_channels=block_in, temb_channels=self.temb_ch, dropout=dropout | |
| ) | |
| self.mid.block_2 = KVAECachedResnetBlock3D( | |
| in_channels=block_in, out_channels=block_in, temb_channels=self.temb_ch, dropout=dropout | |
| ) | |
| self.norm_out = KVAECachedGroupNorm(block_in) | |
| self.conv_out = KVAECachedCausalConv3d( | |
| chan_in=block_in, chan_out=2 * z_channels if double_z else z_channels, kernel_size=3 | |
| ) | |
| self.gradient_checkpointing = False | |
| def forward(self, x: torch.Tensor, cache_dict: Dict) -> torch.Tensor: | |
| temb = None | |
| h = self.conv_in(x, cache=cache_dict["conv_in"]) | |
| for i_level in range(self.num_resolutions): | |
| for i_block in range(self.num_res_blocks): | |
| if torch.is_grad_enabled() and self.gradient_checkpointing: | |
| h = self._gradient_checkpointing_func( | |
| self.down[i_level].block[i_block], h, temb, cache_dict[i_level][i_block] | |
| ) | |
| else: | |
| h = self.down[i_level].block[i_block](h, temb, layer_cache=cache_dict[i_level][i_block]) | |
| if len(self.down[i_level].attn) > 0: | |
| h = self.down[i_level].attn[i_block](h) | |
| if i_level != self.num_resolutions - 1: | |
| h = self.down[i_level].downsample(h, cache=cache_dict[i_level]["down"]) | |
| if torch.is_grad_enabled() and self.gradient_checkpointing: | |
| h = self._gradient_checkpointing_func(self.mid.block_1, h, temb, cache_dict["mid_1"]) | |
| h = self._gradient_checkpointing_func(self.mid.block_2, h, temb, cache_dict["mid_2"]) | |
| else: | |
| h = self.mid.block_1(h, temb, layer_cache=cache_dict["mid_1"]) | |
| h = self.mid.block_2(h, temb, layer_cache=cache_dict["mid_2"]) | |
| h = self.norm_out(h, cache=cache_dict["norm_out"]) | |
| h = nonlinearity(h) | |
| h = self.conv_out(h, cache=cache_dict["conv_out"]) | |
| return h | |
| class KVAECachedDecoder3D(nn.Module): | |
| r""" | |
| Cached 3D Decoder for KVAE. | |
| """ | |
| def __init__( | |
| self, | |
| ch: int = 128, | |
| out_ch: int = 3, | |
| ch_mult: Tuple[int, ...] = (1, 2, 4, 8), | |
| num_res_blocks: int = 2, | |
| dropout: float = 0.0, | |
| z_channels: int = 16, | |
| zq_ch: Optional[int] = None, | |
| add_conv: bool = False, | |
| temporal_compress_times: int = 4, | |
| ): | |
| super().__init__() | |
| self.ch = ch | |
| self.temb_ch = 0 | |
| self.num_resolutions = len(ch_mult) | |
| self.num_res_blocks = num_res_blocks | |
| self.temporal_compress_level = int(np.log2(temporal_compress_times)) | |
| if zq_ch is None: | |
| zq_ch = z_channels | |
| block_in = ch * ch_mult[self.num_resolutions - 1] | |
| self.conv_in = KVAECachedCausalConv3d(chan_in=z_channels, chan_out=block_in, kernel_size=3) | |
| self.mid = nn.Module() | |
| self.mid.block_1 = KVAECachedResnetBlock3D( | |
| in_channels=block_in, | |
| out_channels=block_in, | |
| temb_channels=self.temb_ch, | |
| dropout=dropout, | |
| zq_ch=zq_ch, | |
| add_conv=add_conv, | |
| ) | |
| self.mid.block_2 = KVAECachedResnetBlock3D( | |
| in_channels=block_in, | |
| out_channels=block_in, | |
| temb_channels=self.temb_ch, | |
| dropout=dropout, | |
| zq_ch=zq_ch, | |
| add_conv=add_conv, | |
| ) | |
| self.up = nn.ModuleList() | |
| for i_level in reversed(range(self.num_resolutions)): | |
| block = nn.ModuleList() | |
| attn = nn.ModuleList() | |
| block_out = ch * ch_mult[i_level] | |
| for i_block in range(self.num_res_blocks + 1): | |
| block.append( | |
| KVAECachedResnetBlock3D( | |
| in_channels=block_in, | |
| out_channels=block_out, | |
| temb_channels=self.temb_ch, | |
| dropout=dropout, | |
| zq_ch=zq_ch, | |
| add_conv=add_conv, | |
| ) | |
| ) | |
| block_in = block_out | |
| up = nn.Module() | |
| up.block = block | |
| up.attn = attn | |
| if i_level != 0: | |
| if i_level < self.num_resolutions - self.temporal_compress_level: | |
| up.upsample = KVAECachedPXSUpsample(block_in, compress_time=False) | |
| else: | |
| up.upsample = KVAECachedPXSUpsample(block_in, compress_time=True) | |
| self.up.insert(0, up) | |
| self.norm_out = KVAECachedSpatialNorm3D(block_in, zq_ch, add_conv=add_conv) | |
| self.conv_out = KVAECachedCausalConv3d(chan_in=block_in, chan_out=out_ch, kernel_size=3) | |
| self.gradient_checkpointing = False | |
| def forward(self, z: torch.Tensor, cache_dict: Dict) -> torch.Tensor: | |
| temb = None | |
| zq = z | |
| h = self.conv_in(z, cache_dict["conv_in"]) | |
| if torch.is_grad_enabled() and self.gradient_checkpointing: | |
| h = self._gradient_checkpointing_func(self.mid.block_1, h, temb, cache_dict["mid_1"], zq) | |
| h = self._gradient_checkpointing_func(self.mid.block_2, h, temb, cache_dict["mid_2"], zq) | |
| else: | |
| h = self.mid.block_1(h, temb, layer_cache=cache_dict["mid_1"], zq=zq) | |
| h = self.mid.block_2(h, temb, layer_cache=cache_dict["mid_2"], zq=zq) | |
| for i_level in reversed(range(self.num_resolutions)): | |
| for i_block in range(self.num_res_blocks + 1): | |
| if torch.is_grad_enabled() and self.gradient_checkpointing: | |
| h = self._gradient_checkpointing_func( | |
| self.up[i_level].block[i_block], h, temb, cache_dict[i_level][i_block], zq | |
| ) | |
| else: | |
| h = self.up[i_level].block[i_block](h, temb, layer_cache=cache_dict[i_level][i_block], zq=zq) | |
| if len(self.up[i_level].attn) > 0: | |
| h = self.up[i_level].attn[i_block](h, zq) | |
| if i_level != 0: | |
| h = self.up[i_level].upsample(h, cache_dict[i_level]["up"]) | |
| h = self.norm_out(h, zq, cache_dict["norm_out"]) | |
| h = nonlinearity(h) | |
| h = self.conv_out(h, cache_dict["conv_out"]) | |
| return h | |
| # ============================================================================= | |
| # Main AutoencoderKL class | |
| # ============================================================================= | |
| class AutoencoderKLKVAEVideo(ModelMixin, AutoencoderMixin, ConfigMixin, FromOriginalModelMixin): | |
| r""" | |
| A VAE model with KL loss for encoding videos into latents and decoding latent representations into videos. Used in | |
| [KVAE](https://github.com/kandinskylab/kvae-1). | |
| This model inherits from [`ModelMixin`]. Check the superclass documentation for its generic methods implemented for | |
| all models (such as downloading or saving). | |
| Parameters: | |
| ch (`int`, *optional*, defaults to 128): Base channel count. | |
| ch_mult (`Tuple[int]`, *optional*, defaults to `(1, 2, 4, 8)`): Channel multipliers per level. | |
| num_res_blocks (`int`, *optional*, defaults to 2): Number of residual blocks per level. | |
| in_channels (`int`, *optional*, defaults to 3): Number of input channels. | |
| out_ch (`int`, *optional*, defaults to 3): Number of output channels. | |
| z_channels (`int`, *optional*, defaults to 16): Number of latent channels. | |
| temporal_compress_times (`int`, *optional*, defaults to 4): Temporal compression factor. | |
| """ | |
| _supports_gradient_checkpointing = True | |
| _no_split_modules = ["KVAECachedResnetBlock3D"] | |
| def __init__( | |
| self, | |
| ch: int = 128, | |
| ch_mult: Tuple[int, ...] = (1, 2, 4, 8), | |
| num_res_blocks: int = 2, | |
| in_channels: int = 3, | |
| out_ch: int = 3, | |
| z_channels: int = 16, | |
| temporal_compress_times: int = 4, | |
| ): | |
| super().__init__() | |
| self.encoder = KVAECachedEncoder3D( | |
| ch=ch, | |
| ch_mult=ch_mult, | |
| num_res_blocks=num_res_blocks, | |
| in_channels=in_channels, | |
| z_channels=z_channels, | |
| double_z=True, | |
| temporal_compress_times=temporal_compress_times, | |
| ) | |
| self.decoder = KVAECachedDecoder3D( | |
| ch=ch, | |
| ch_mult=ch_mult, | |
| num_res_blocks=num_res_blocks, | |
| out_ch=out_ch, | |
| z_channels=z_channels, | |
| temporal_compress_times=temporal_compress_times, | |
| ) | |
| self.use_slicing = False | |
| self.use_tiling = False | |
| def _make_encoder_cache(self) -> Dict: | |
| """Create empty cache for cached encoder.""" | |
| def make_dict(name, p=None): | |
| if name == "conv": | |
| return {"padding": None} | |
| layer, module = name.split("_") | |
| if layer == "norm": | |
| if module == "enc": | |
| return {"mean": None, "var": None} | |
| else: | |
| return {"norm": make_dict("norm_enc"), "add_conv": make_dict("conv")} | |
| elif layer == "resblock": | |
| return { | |
| "norm1": make_dict(f"norm_{module}"), | |
| "norm2": make_dict(f"norm_{module}"), | |
| "conv1": make_dict("conv"), | |
| "conv2": make_dict("conv"), | |
| "conv_shortcut": make_dict("conv"), | |
| } | |
| elif layer.isdigit(): | |
| out_dict = {"down": [make_dict("conv"), make_dict("conv")], "up": make_dict("conv")} | |
| for i in range(p): | |
| out_dict[i] = make_dict(f"resblock_{module}") | |
| return out_dict | |
| cache = { | |
| "conv_in": make_dict("conv"), | |
| "mid_1": make_dict("resblock_enc"), | |
| "mid_2": make_dict("resblock_enc"), | |
| "norm_out": make_dict("norm_enc"), | |
| "conv_out": make_dict("conv"), | |
| } | |
| # Encoder uses num_res_blocks per level | |
| for i in range(len(self.config.ch_mult)): | |
| cache[i] = make_dict(f"{i}_enc", p=self.config.num_res_blocks) | |
| return cache | |
| def _make_decoder_cache(self) -> Dict: | |
| """Create empty cache for decoder.""" | |
| def make_dict(name, p=None): | |
| if name == "conv": | |
| return {"padding": None} | |
| layer, module = name.split("_") | |
| if layer == "norm": | |
| if module == "enc": | |
| return {"mean": None, "var": None} | |
| else: | |
| return {"norm": make_dict("norm_enc"), "add_conv": make_dict("conv")} | |
| elif layer == "resblock": | |
| return { | |
| "norm1": make_dict(f"norm_{module}"), | |
| "norm2": make_dict(f"norm_{module}"), | |
| "conv1": make_dict("conv"), | |
| "conv2": make_dict("conv"), | |
| "conv_shortcut": make_dict("conv"), | |
| } | |
| elif layer.isdigit(): | |
| out_dict = {"down": [make_dict("conv"), make_dict("conv")], "up": make_dict("conv")} | |
| for i in range(p): | |
| out_dict[i] = make_dict(f"resblock_{module}") | |
| return out_dict | |
| cache = { | |
| "conv_in": make_dict("conv"), | |
| "mid_1": make_dict("resblock_dec"), | |
| "mid_2": make_dict("resblock_dec"), | |
| "norm_out": make_dict("norm_dec"), | |
| "conv_out": make_dict("conv"), | |
| } | |
| for i in range(len(self.config.ch_mult)): | |
| cache[i] = make_dict(f"{i}_dec", p=self.config.num_res_blocks + 1) | |
| return cache | |
| def enable_slicing(self) -> None: | |
| r"""Enable sliced VAE decoding.""" | |
| self.use_slicing = True | |
| def disable_slicing(self) -> None: | |
| r"""Disable sliced VAE decoding.""" | |
| self.use_slicing = False | |
| def _encode(self, x: torch.Tensor, seg_len: int = 16) -> torch.Tensor: | |
| # Cached encoder processes by segments | |
| cache = self._make_encoder_cache() | |
| split_list = [seg_len + 1] | |
| n_frames = x.size(2) - (seg_len + 1) | |
| while n_frames > 0: | |
| split_list.append(seg_len) | |
| n_frames -= seg_len | |
| split_list[-1] += n_frames | |
| latent = [] | |
| for chunk in torch.split(x, split_list, dim=2): | |
| l = self.encoder(chunk, cache) | |
| sample, _ = torch.chunk(l, 2, dim=1) | |
| latent.append(sample) | |
| return torch.cat(latent, dim=2) | |
| def encode( | |
| self, x: torch.Tensor, return_dict: bool = True | |
| ) -> Union[AutoencoderKLOutput, Tuple[DiagonalGaussianDistribution]]: | |
| """ | |
| Encode a batch of videos into latents. | |
| Args: | |
| x (`torch.Tensor`): Input batch of videos with shape (B, C, T, H, W). | |
| return_dict (`bool`, *optional*, defaults to `True`): | |
| Whether to return a [`~models.autoencoder_kl.AutoencoderKLOutput`] instead of a plain tuple. | |
| Returns: | |
| The latent representations of the encoded videos. | |
| """ | |
| if self.use_slicing and x.shape[0] > 1: | |
| encoded_slices = [self._encode(x_slice) for x_slice in x.split(1)] | |
| h = torch.cat(encoded_slices) | |
| else: | |
| h = self._encode(x) | |
| # For cached encoder, we already did the split in _encode | |
| h_double = torch.cat([h, torch.zeros_like(h)], dim=1) | |
| posterior = DiagonalGaussianDistribution(h_double) | |
| if not return_dict: | |
| return (posterior,) | |
| return AutoencoderKLOutput(latent_dist=posterior) | |
| def _decode(self, z: torch.Tensor, seg_len: int = 16) -> torch.Tensor: | |
| cache = self._make_decoder_cache() | |
| temporal_compress = self.config.temporal_compress_times | |
| split_list = [seg_len + 1] | |
| n_frames = temporal_compress * (z.size(2) - 1) - seg_len | |
| while n_frames > 0: | |
| split_list.append(seg_len) | |
| n_frames -= seg_len | |
| split_list[-1] += n_frames | |
| split_list = [math.ceil(size / temporal_compress) for size in split_list] | |
| recs = [] | |
| for chunk in torch.split(z, split_list, dim=2): | |
| out = self.decoder(chunk, cache) | |
| recs.append(out) | |
| return torch.cat(recs, dim=2) | |
| def decode(self, z: torch.Tensor, return_dict: bool = True) -> Union[DecoderOutput, torch.Tensor]: | |
| """ | |
| Decode a batch of videos. | |
| Args: | |
| z (`torch.Tensor`): Input batch of latent vectors with shape (B, C, T, H, W). | |
| return_dict (`bool`, *optional*, defaults to `True`): | |
| Whether to return a [`~models.vae.DecoderOutput`] instead of a plain tuple. | |
| Returns: | |
| [`~models.vae.DecoderOutput`] or `tuple`: Decoded video. | |
| """ | |
| if self.use_slicing and z.shape[0] > 1: | |
| decoded_slices = [self._decode(z_slice) for z_slice in z.split(1)] | |
| decoded = torch.cat(decoded_slices) | |
| else: | |
| decoded = self._decode(z) | |
| if not return_dict: | |
| return (decoded,) | |
| return DecoderOutput(sample=decoded) | |
| def forward( | |
| self, | |
| sample: torch.Tensor, | |
| sample_posterior: bool = False, | |
| return_dict: bool = True, | |
| generator: Optional[torch.Generator] = None, | |
| ) -> Union[DecoderOutput, torch.Tensor]: | |
| r""" | |
| Args: | |
| sample (`torch.Tensor`): Input sample. | |
| sample_posterior (`bool`, *optional*, defaults to `False`): | |
| Whether to sample from the posterior. | |
| return_dict (`bool`, *optional*, defaults to `True`): | |
| Whether or not to return a [`DecoderOutput`] instead of a plain tuple. | |
| generator (`torch.Generator`, *optional*): | |
| A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make sampling | |
| deterministic. | |
| Returns: | |
| [`~models.vae.DecoderOutput`] or `tuple`: | |
| If `return_dict` is True, a [`~models.vae.DecoderOutput`] is returned, otherwise a plain `tuple` is | |
| returned. | |
| """ | |
| x = sample | |
| posterior = self.encode(x).latent_dist | |
| if sample_posterior: | |
| z = posterior.sample(generator=generator) | |
| else: | |
| z = posterior.mode() | |
| dec = self.decode(z).sample | |
| if not return_dict: | |
| return (dec,) | |
| return DecoderOutput(sample=dec) | |