Fabrice-TIERCELIN commited on
Commit
9f58fc6
·
verified ·
1 Parent(s): 12f3ba1

Upload 10 files

Browse files
ltx_video/models/autoencoders/causal_conv3d.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple, Union
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+
7
+ class CausalConv3d(nn.Module):
8
+ def __init__(
9
+ self,
10
+ in_channels,
11
+ out_channels,
12
+ kernel_size: int = 3,
13
+ stride: Union[int, Tuple[int]] = 1,
14
+ dilation: int = 1,
15
+ groups: int = 1,
16
+ spatial_padding_mode: str = "zeros",
17
+ **kwargs,
18
+ ):
19
+ super().__init__()
20
+
21
+ self.in_channels = in_channels
22
+ self.out_channels = out_channels
23
+
24
+ kernel_size = (kernel_size, kernel_size, kernel_size)
25
+ self.time_kernel_size = kernel_size[0]
26
+
27
+ dilation = (dilation, 1, 1)
28
+
29
+ height_pad = kernel_size[1] // 2
30
+ width_pad = kernel_size[2] // 2
31
+ padding = (0, height_pad, width_pad)
32
+
33
+ self.conv = nn.Conv3d(
34
+ in_channels,
35
+ out_channels,
36
+ kernel_size,
37
+ stride=stride,
38
+ dilation=dilation,
39
+ padding=padding,
40
+ padding_mode=spatial_padding_mode,
41
+ groups=groups,
42
+ )
43
+
44
+ def forward(self, x, causal: bool = True):
45
+ if causal:
46
+ first_frame_pad = x[:, :, :1, :, :].repeat(
47
+ (1, 1, self.time_kernel_size - 1, 1, 1)
48
+ )
49
+ x = torch.concatenate((first_frame_pad, x), dim=2)
50
+ else:
51
+ first_frame_pad = x[:, :, :1, :, :].repeat(
52
+ (1, 1, (self.time_kernel_size - 1) // 2, 1, 1)
53
+ )
54
+ last_frame_pad = x[:, :, -1:, :, :].repeat(
55
+ (1, 1, (self.time_kernel_size - 1) // 2, 1, 1)
56
+ )
57
+ x = torch.concatenate((first_frame_pad, x, last_frame_pad), dim=2)
58
+ x = self.conv(x)
59
+ return x
60
+
61
+ @property
62
+ def weight(self):
63
+ return self.conv.weight
ltx_video/models/autoencoders/causal_video_autoencoder.py ADDED
@@ -0,0 +1,1403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from functools import partial
4
+ from types import SimpleNamespace
5
+ from typing import Any, Mapping, Optional, Tuple, Union, List
6
+ from pathlib import Path
7
+
8
+ import torch
9
+ import numpy as np
10
+ from einops import rearrange
11
+ from torch import nn
12
+ from diffusers.utils import logging
13
+ import torch.nn.functional as F
14
+ from diffusers.models.embeddings import PixArtAlphaCombinedTimestepSizeEmbeddings
15
+ from safetensors import safe_open
16
+
17
+
18
+ from ltx_video.models.autoencoders.conv_nd_factory import make_conv_nd, make_linear_nd
19
+ from ltx_video.models.autoencoders.pixel_norm import PixelNorm
20
+ from ltx_video.models.autoencoders.pixel_shuffle import PixelShuffleND
21
+ from ltx_video.models.autoencoders.vae import AutoencoderKLWrapper
22
+ from ltx_video.models.transformers.attention import Attention
23
+ from ltx_video.utils.diffusers_config_mapping import (
24
+ diffusers_and_ours_config_mapping,
25
+ make_hashable_key,
26
+ VAE_KEYS_RENAME_DICT,
27
+ )
28
+
29
+ PER_CHANNEL_STATISTICS_PREFIX = "per_channel_statistics."
30
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
31
+
32
+
33
+ class CausalVideoAutoencoder(AutoencoderKLWrapper):
34
+ @classmethod
35
+ def from_pretrained(
36
+ cls,
37
+ pretrained_model_name_or_path: Optional[Union[str, os.PathLike]],
38
+ *args,
39
+ **kwargs,
40
+ ):
41
+ pretrained_model_name_or_path = Path(pretrained_model_name_or_path)
42
+ if (
43
+ pretrained_model_name_or_path.is_dir()
44
+ and (pretrained_model_name_or_path / "autoencoder.pth").exists()
45
+ ):
46
+ config_local_path = pretrained_model_name_or_path / "config.json"
47
+ config = cls.load_config(config_local_path, **kwargs)
48
+
49
+ model_local_path = pretrained_model_name_or_path / "autoencoder.pth"
50
+ state_dict = torch.load(model_local_path, map_location=torch.device("cpu"))
51
+
52
+ statistics_local_path = (
53
+ pretrained_model_name_or_path / "per_channel_statistics.json"
54
+ )
55
+ if statistics_local_path.exists():
56
+ with open(statistics_local_path, "r") as file:
57
+ data = json.load(file)
58
+ transposed_data = list(zip(*data["data"]))
59
+ data_dict = {
60
+ col: torch.tensor(vals)
61
+ for col, vals in zip(data["columns"], transposed_data)
62
+ }
63
+ std_of_means = data_dict["std-of-means"]
64
+ mean_of_means = data_dict.get(
65
+ "mean-of-means", torch.zeros_like(data_dict["std-of-means"])
66
+ )
67
+ state_dict[f"{PER_CHANNEL_STATISTICS_PREFIX}std-of-means"] = (
68
+ std_of_means
69
+ )
70
+ state_dict[f"{PER_CHANNEL_STATISTICS_PREFIX}mean-of-means"] = (
71
+ mean_of_means
72
+ )
73
+
74
+ elif pretrained_model_name_or_path.is_dir():
75
+ config_path = pretrained_model_name_or_path / "vae" / "config.json"
76
+ with open(config_path, "r") as f:
77
+ config = make_hashable_key(json.load(f))
78
+
79
+ assert config in diffusers_and_ours_config_mapping, (
80
+ "Provided diffusers checkpoint config for VAE is not suppported. "
81
+ "We only support diffusers configs found in Lightricks/LTX-Video."
82
+ )
83
+
84
+ config = diffusers_and_ours_config_mapping[config]
85
+
86
+ state_dict_path = (
87
+ pretrained_model_name_or_path
88
+ / "vae"
89
+ / "diffusion_pytorch_model.safetensors"
90
+ )
91
+
92
+ state_dict = {}
93
+ with safe_open(state_dict_path, framework="pt", device="cpu") as f:
94
+ for k in f.keys():
95
+ state_dict[k] = f.get_tensor(k)
96
+ for key in list(state_dict.keys()):
97
+ new_key = key
98
+ for replace_key, rename_key in VAE_KEYS_RENAME_DICT.items():
99
+ new_key = new_key.replace(replace_key, rename_key)
100
+
101
+ state_dict[new_key] = state_dict.pop(key)
102
+
103
+ elif pretrained_model_name_or_path.is_file() and str(
104
+ pretrained_model_name_or_path
105
+ ).endswith(".safetensors"):
106
+ state_dict = {}
107
+ with safe_open(
108
+ pretrained_model_name_or_path, framework="pt", device="cpu"
109
+ ) as f:
110
+ metadata = f.metadata()
111
+ for k in f.keys():
112
+ state_dict[k] = f.get_tensor(k)
113
+ configs = json.loads(metadata["config"])
114
+ config = configs["vae"]
115
+
116
+ video_vae = cls.from_config(config)
117
+ if "torch_dtype" in kwargs:
118
+ video_vae.to(kwargs["torch_dtype"])
119
+ video_vae.load_state_dict(state_dict)
120
+ return video_vae
121
+
122
+ @staticmethod
123
+ def from_config(config):
124
+ assert (
125
+ config["_class_name"] == "CausalVideoAutoencoder"
126
+ ), "config must have _class_name=CausalVideoAutoencoder"
127
+ if isinstance(config["dims"], list):
128
+ config["dims"] = tuple(config["dims"])
129
+
130
+ assert config["dims"] in [2, 3, (2, 1)], "dims must be 2, 3 or (2, 1)"
131
+
132
+ double_z = config.get("double_z", True)
133
+ latent_log_var = config.get(
134
+ "latent_log_var", "per_channel" if double_z else "none"
135
+ )
136
+ use_quant_conv = config.get("use_quant_conv", True)
137
+ normalize_latent_channels = config.get("normalize_latent_channels", False)
138
+
139
+ if use_quant_conv and latent_log_var in ["uniform", "constant"]:
140
+ raise ValueError(
141
+ f"latent_log_var={latent_log_var} requires use_quant_conv=False"
142
+ )
143
+
144
+ encoder = Encoder(
145
+ dims=config["dims"],
146
+ in_channels=config.get("in_channels", 3),
147
+ out_channels=config["latent_channels"],
148
+ blocks=config.get("encoder_blocks", config.get("blocks")),
149
+ patch_size=config.get("patch_size", 1),
150
+ latent_log_var=latent_log_var,
151
+ norm_layer=config.get("norm_layer", "group_norm"),
152
+ base_channels=config.get("encoder_base_channels", 128),
153
+ spatial_padding_mode=config.get("spatial_padding_mode", "zeros"),
154
+ )
155
+
156
+ decoder = Decoder(
157
+ dims=config["dims"],
158
+ in_channels=config["latent_channels"],
159
+ out_channels=config.get("out_channels", 3),
160
+ blocks=config.get("decoder_blocks", config.get("blocks")),
161
+ patch_size=config.get("patch_size", 1),
162
+ norm_layer=config.get("norm_layer", "group_norm"),
163
+ causal=config.get("causal_decoder", False),
164
+ timestep_conditioning=config.get("timestep_conditioning", False),
165
+ base_channels=config.get("decoder_base_channels", 128),
166
+ spatial_padding_mode=config.get("spatial_padding_mode", "zeros"),
167
+ )
168
+
169
+ dims = config["dims"]
170
+ return CausalVideoAutoencoder(
171
+ encoder=encoder,
172
+ decoder=decoder,
173
+ latent_channels=config["latent_channels"],
174
+ dims=dims,
175
+ use_quant_conv=use_quant_conv,
176
+ normalize_latent_channels=normalize_latent_channels,
177
+ )
178
+
179
+ @property
180
+ def config(self):
181
+ return SimpleNamespace(
182
+ _class_name="CausalVideoAutoencoder",
183
+ dims=self.dims,
184
+ in_channels=self.encoder.conv_in.in_channels // self.encoder.patch_size**2,
185
+ out_channels=self.decoder.conv_out.out_channels
186
+ // self.decoder.patch_size**2,
187
+ latent_channels=self.decoder.conv_in.in_channels,
188
+ encoder_blocks=self.encoder.blocks_desc,
189
+ decoder_blocks=self.decoder.blocks_desc,
190
+ scaling_factor=1.0,
191
+ norm_layer=self.encoder.norm_layer,
192
+ patch_size=self.encoder.patch_size,
193
+ latent_log_var=self.encoder.latent_log_var,
194
+ use_quant_conv=self.use_quant_conv,
195
+ causal_decoder=self.decoder.causal,
196
+ timestep_conditioning=self.decoder.timestep_conditioning,
197
+ normalize_latent_channels=self.normalize_latent_channels,
198
+ )
199
+
200
+ @property
201
+ def is_video_supported(self):
202
+ """
203
+ Check if the model supports video inputs of shape (B, C, F, H, W). Otherwise, the model only supports 2D images.
204
+ """
205
+ return self.dims != 2
206
+
207
+ @property
208
+ def spatial_downscale_factor(self):
209
+ return (
210
+ 2
211
+ ** len(
212
+ [
213
+ block
214
+ for block in self.encoder.blocks_desc
215
+ if block[0]
216
+ in [
217
+ "compress_space",
218
+ "compress_all",
219
+ "compress_all_res",
220
+ "compress_space_res",
221
+ ]
222
+ ]
223
+ )
224
+ * self.encoder.patch_size
225
+ )
226
+
227
+ @property
228
+ def temporal_downscale_factor(self):
229
+ return 2 ** len(
230
+ [
231
+ block
232
+ for block in self.encoder.blocks_desc
233
+ if block[0]
234
+ in [
235
+ "compress_time",
236
+ "compress_all",
237
+ "compress_all_res",
238
+ "compress_space_res",
239
+ ]
240
+ ]
241
+ )
242
+
243
+ def to_json_string(self) -> str:
244
+ import json
245
+
246
+ return json.dumps(self.config.__dict__)
247
+
248
+ def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True):
249
+ if any([key.startswith("vae.") for key in state_dict.keys()]):
250
+ state_dict = {
251
+ key.replace("vae.", ""): value
252
+ for key, value in state_dict.items()
253
+ if key.startswith("vae.")
254
+ }
255
+ ckpt_state_dict = {
256
+ key: value
257
+ for key, value in state_dict.items()
258
+ if not key.startswith(PER_CHANNEL_STATISTICS_PREFIX)
259
+ }
260
+
261
+ model_keys = set(name for name, _ in self.named_modules())
262
+
263
+ key_mapping = {
264
+ ".resnets.": ".res_blocks.",
265
+ "downsamplers.0": "downsample",
266
+ "upsamplers.0": "upsample",
267
+ }
268
+ converted_state_dict = {}
269
+ for key, value in ckpt_state_dict.items():
270
+ for k, v in key_mapping.items():
271
+ key = key.replace(k, v)
272
+
273
+ key_prefix = ".".join(key.split(".")[:-1])
274
+ if "norm" in key and key_prefix not in model_keys:
275
+ logger.info(
276
+ f"Removing key {key} from state_dict as it is not present in the model"
277
+ )
278
+ continue
279
+
280
+ converted_state_dict[key] = value
281
+
282
+ super().load_state_dict(converted_state_dict, strict=strict)
283
+
284
+ data_dict = {
285
+ key.removeprefix(PER_CHANNEL_STATISTICS_PREFIX): value
286
+ for key, value in state_dict.items()
287
+ if key.startswith(PER_CHANNEL_STATISTICS_PREFIX)
288
+ }
289
+ if len(data_dict) > 0:
290
+ self.register_buffer("std_of_means", data_dict["std-of-means"])
291
+ self.register_buffer(
292
+ "mean_of_means",
293
+ data_dict.get(
294
+ "mean-of-means", torch.zeros_like(data_dict["std-of-means"])
295
+ ),
296
+ )
297
+
298
+ def last_layer(self):
299
+ if hasattr(self.decoder, "conv_out"):
300
+ if isinstance(self.decoder.conv_out, nn.Sequential):
301
+ last_layer = self.decoder.conv_out[-1]
302
+ else:
303
+ last_layer = self.decoder.conv_out
304
+ else:
305
+ last_layer = self.decoder.layers[-1]
306
+ return last_layer
307
+
308
+ def set_use_tpu_flash_attention(self):
309
+ for block in self.decoder.up_blocks:
310
+ if isinstance(block, UNetMidBlock3D) and block.attention_blocks:
311
+ for attention_block in block.attention_blocks:
312
+ attention_block.set_use_tpu_flash_attention()
313
+
314
+
315
+ class Encoder(nn.Module):
316
+ r"""
317
+ The `Encoder` layer of a variational autoencoder that encodes its input into a latent representation.
318
+
319
+ Args:
320
+ dims (`int` or `Tuple[int, int]`, *optional*, defaults to 3):
321
+ The number of dimensions to use in convolutions.
322
+ in_channels (`int`, *optional*, defaults to 3):
323
+ The number of input channels.
324
+ out_channels (`int`, *optional*, defaults to 3):
325
+ The number of output channels.
326
+ blocks (`List[Tuple[str, int]]`, *optional*, defaults to `[("res_x", 1)]`):
327
+ The blocks to use. Each block is a tuple of the block name and the number of layers.
328
+ base_channels (`int`, *optional*, defaults to 128):
329
+ The number of output channels for the first convolutional layer.
330
+ norm_num_groups (`int`, *optional*, defaults to 32):
331
+ The number of groups for normalization.
332
+ patch_size (`int`, *optional*, defaults to 1):
333
+ The patch size to use. Should be a power of 2.
334
+ norm_layer (`str`, *optional*, defaults to `group_norm`):
335
+ The normalization layer to use. Can be either `group_norm` or `pixel_norm`.
336
+ latent_log_var (`str`, *optional*, defaults to `per_channel`):
337
+ The number of channels for the log variance. Can be either `per_channel`, `uniform`, `constant` or `none`.
338
+ """
339
+
340
+ def __init__(
341
+ self,
342
+ dims: Union[int, Tuple[int, int]] = 3,
343
+ in_channels: int = 3,
344
+ out_channels: int = 3,
345
+ blocks: List[Tuple[str, int | dict]] = [("res_x", 1)],
346
+ base_channels: int = 128,
347
+ norm_num_groups: int = 32,
348
+ patch_size: Union[int, Tuple[int]] = 1,
349
+ norm_layer: str = "group_norm", # group_norm, pixel_norm
350
+ latent_log_var: str = "per_channel",
351
+ spatial_padding_mode: str = "zeros",
352
+ ):
353
+ super().__init__()
354
+ self.patch_size = patch_size
355
+ self.norm_layer = norm_layer
356
+ self.latent_channels = out_channels
357
+ self.latent_log_var = latent_log_var
358
+ self.blocks_desc = blocks
359
+
360
+ in_channels = in_channels * patch_size**2
361
+ output_channel = base_channels
362
+
363
+ self.conv_in = make_conv_nd(
364
+ dims=dims,
365
+ in_channels=in_channels,
366
+ out_channels=output_channel,
367
+ kernel_size=3,
368
+ stride=1,
369
+ padding=1,
370
+ causal=True,
371
+ spatial_padding_mode=spatial_padding_mode,
372
+ )
373
+
374
+ self.down_blocks = nn.ModuleList([])
375
+
376
+ for block_name, block_params in blocks:
377
+ input_channel = output_channel
378
+ if isinstance(block_params, int):
379
+ block_params = {"num_layers": block_params}
380
+
381
+ if block_name == "res_x":
382
+ block = UNetMidBlock3D(
383
+ dims=dims,
384
+ in_channels=input_channel,
385
+ num_layers=block_params["num_layers"],
386
+ resnet_eps=1e-6,
387
+ resnet_groups=norm_num_groups,
388
+ norm_layer=norm_layer,
389
+ spatial_padding_mode=spatial_padding_mode,
390
+ )
391
+ elif block_name == "res_x_y":
392
+ output_channel = block_params.get("multiplier", 2) * output_channel
393
+ block = ResnetBlock3D(
394
+ dims=dims,
395
+ in_channels=input_channel,
396
+ out_channels=output_channel,
397
+ eps=1e-6,
398
+ groups=norm_num_groups,
399
+ norm_layer=norm_layer,
400
+ spatial_padding_mode=spatial_padding_mode,
401
+ )
402
+ elif block_name == "compress_time":
403
+ block = make_conv_nd(
404
+ dims=dims,
405
+ in_channels=input_channel,
406
+ out_channels=output_channel,
407
+ kernel_size=3,
408
+ stride=(2, 1, 1),
409
+ causal=True,
410
+ spatial_padding_mode=spatial_padding_mode,
411
+ )
412
+ elif block_name == "compress_space":
413
+ block = make_conv_nd(
414
+ dims=dims,
415
+ in_channels=input_channel,
416
+ out_channels=output_channel,
417
+ kernel_size=3,
418
+ stride=(1, 2, 2),
419
+ causal=True,
420
+ spatial_padding_mode=spatial_padding_mode,
421
+ )
422
+ elif block_name == "compress_all":
423
+ block = make_conv_nd(
424
+ dims=dims,
425
+ in_channels=input_channel,
426
+ out_channels=output_channel,
427
+ kernel_size=3,
428
+ stride=(2, 2, 2),
429
+ causal=True,
430
+ spatial_padding_mode=spatial_padding_mode,
431
+ )
432
+ elif block_name == "compress_all_x_y":
433
+ output_channel = block_params.get("multiplier", 2) * output_channel
434
+ block = make_conv_nd(
435
+ dims=dims,
436
+ in_channels=input_channel,
437
+ out_channels=output_channel,
438
+ kernel_size=3,
439
+ stride=(2, 2, 2),
440
+ causal=True,
441
+ spatial_padding_mode=spatial_padding_mode,
442
+ )
443
+ elif block_name == "compress_all_res":
444
+ output_channel = block_params.get("multiplier", 2) * output_channel
445
+ block = SpaceToDepthDownsample(
446
+ dims=dims,
447
+ in_channels=input_channel,
448
+ out_channels=output_channel,
449
+ stride=(2, 2, 2),
450
+ spatial_padding_mode=spatial_padding_mode,
451
+ )
452
+ elif block_name == "compress_space_res":
453
+ output_channel = block_params.get("multiplier", 2) * output_channel
454
+ block = SpaceToDepthDownsample(
455
+ dims=dims,
456
+ in_channels=input_channel,
457
+ out_channels=output_channel,
458
+ stride=(1, 2, 2),
459
+ spatial_padding_mode=spatial_padding_mode,
460
+ )
461
+ elif block_name == "compress_time_res":
462
+ output_channel = block_params.get("multiplier", 2) * output_channel
463
+ block = SpaceToDepthDownsample(
464
+ dims=dims,
465
+ in_channels=input_channel,
466
+ out_channels=output_channel,
467
+ stride=(2, 1, 1),
468
+ spatial_padding_mode=spatial_padding_mode,
469
+ )
470
+ else:
471
+ raise ValueError(f"unknown block: {block_name}")
472
+
473
+ self.down_blocks.append(block)
474
+
475
+ # out
476
+ if norm_layer == "group_norm":
477
+ self.conv_norm_out = nn.GroupNorm(
478
+ num_channels=output_channel, num_groups=norm_num_groups, eps=1e-6
479
+ )
480
+ elif norm_layer == "pixel_norm":
481
+ self.conv_norm_out = PixelNorm()
482
+ elif norm_layer == "layer_norm":
483
+ self.conv_norm_out = LayerNorm(output_channel, eps=1e-6)
484
+
485
+ self.conv_act = nn.SiLU()
486
+
487
+ conv_out_channels = out_channels
488
+ if latent_log_var == "per_channel":
489
+ conv_out_channels *= 2
490
+ elif latent_log_var == "uniform":
491
+ conv_out_channels += 1
492
+ elif latent_log_var == "constant":
493
+ conv_out_channels += 1
494
+ elif latent_log_var != "none":
495
+ raise ValueError(f"Invalid latent_log_var: {latent_log_var}")
496
+ self.conv_out = make_conv_nd(
497
+ dims,
498
+ output_channel,
499
+ conv_out_channels,
500
+ 3,
501
+ padding=1,
502
+ causal=True,
503
+ spatial_padding_mode=spatial_padding_mode,
504
+ )
505
+
506
+ self.gradient_checkpointing = False
507
+
508
+ def forward(self, sample: torch.FloatTensor) -> torch.FloatTensor:
509
+ r"""The forward method of the `Encoder` class."""
510
+
511
+ sample = patchify(sample, patch_size_hw=self.patch_size, patch_size_t=1)
512
+ sample = self.conv_in(sample)
513
+
514
+ checkpoint_fn = (
515
+ partial(torch.utils.checkpoint.checkpoint, use_reentrant=False)
516
+ if self.gradient_checkpointing and self.training
517
+ else lambda x: x
518
+ )
519
+
520
+ for down_block in self.down_blocks:
521
+ sample = checkpoint_fn(down_block)(sample)
522
+
523
+ sample = self.conv_norm_out(sample)
524
+ sample = self.conv_act(sample)
525
+ sample = self.conv_out(sample)
526
+
527
+ if self.latent_log_var == "uniform":
528
+ last_channel = sample[:, -1:, ...]
529
+ num_dims = sample.dim()
530
+
531
+ if num_dims == 4:
532
+ # For shape (B, C, H, W)
533
+ repeated_last_channel = last_channel.repeat(
534
+ 1, sample.shape[1] - 2, 1, 1
535
+ )
536
+ sample = torch.cat([sample, repeated_last_channel], dim=1)
537
+ elif num_dims == 5:
538
+ # For shape (B, C, F, H, W)
539
+ repeated_last_channel = last_channel.repeat(
540
+ 1, sample.shape[1] - 2, 1, 1, 1
541
+ )
542
+ sample = torch.cat([sample, repeated_last_channel], dim=1)
543
+ else:
544
+ raise ValueError(f"Invalid input shape: {sample.shape}")
545
+ elif self.latent_log_var == "constant":
546
+ sample = sample[:, :-1, ...]
547
+ approx_ln_0 = (
548
+ -30
549
+ ) # this is the minimal clamp value in DiagonalGaussianDistribution objects
550
+ sample = torch.cat(
551
+ [sample, torch.ones_like(sample, device=sample.device) * approx_ln_0],
552
+ dim=1,
553
+ )
554
+
555
+ return sample
556
+
557
+
558
+ class Decoder(nn.Module):
559
+ r"""
560
+ The `Decoder` layer of a variational autoencoder that decodes its latent representation into an output sample.
561
+
562
+ Args:
563
+ dims (`int` or `Tuple[int, int]`, *optional*, defaults to 3):
564
+ The number of dimensions to use in convolutions.
565
+ in_channels (`int`, *optional*, defaults to 3):
566
+ The number of input channels.
567
+ out_channels (`int`, *optional*, defaults to 3):
568
+ The number of output channels.
569
+ blocks (`List[Tuple[str, int]]`, *optional*, defaults to `[("res_x", 1)]`):
570
+ The blocks to use. Each block is a tuple of the block name and the number of layers.
571
+ base_channels (`int`, *optional*, defaults to 128):
572
+ The number of output channels for the first convolutional layer.
573
+ norm_num_groups (`int`, *optional*, defaults to 32):
574
+ The number of groups for normalization.
575
+ patch_size (`int`, *optional*, defaults to 1):
576
+ The patch size to use. Should be a power of 2.
577
+ norm_layer (`str`, *optional*, defaults to `group_norm`):
578
+ The normalization layer to use. Can be either `group_norm` or `pixel_norm`.
579
+ causal (`bool`, *optional*, defaults to `True`):
580
+ Whether to use causal convolutions or not.
581
+ """
582
+
583
+ def __init__(
584
+ self,
585
+ dims,
586
+ in_channels: int = 3,
587
+ out_channels: int = 3,
588
+ blocks: List[Tuple[str, int | dict]] = [("res_x", 1)],
589
+ base_channels: int = 128,
590
+ layers_per_block: int = 2,
591
+ norm_num_groups: int = 32,
592
+ patch_size: int = 1,
593
+ norm_layer: str = "group_norm",
594
+ causal: bool = True,
595
+ timestep_conditioning: bool = False,
596
+ spatial_padding_mode: str = "zeros",
597
+ ):
598
+ super().__init__()
599
+ self.patch_size = patch_size
600
+ self.layers_per_block = layers_per_block
601
+ out_channels = out_channels * patch_size**2
602
+ self.causal = causal
603
+ self.blocks_desc = blocks
604
+
605
+ # Compute output channel to be product of all channel-multiplier blocks
606
+ output_channel = base_channels
607
+ for block_name, block_params in list(reversed(blocks)):
608
+ block_params = block_params if isinstance(block_params, dict) else {}
609
+ if block_name == "res_x_y":
610
+ output_channel = output_channel * block_params.get("multiplier", 2)
611
+ if block_name == "compress_all":
612
+ output_channel = output_channel * block_params.get("multiplier", 1)
613
+
614
+ self.conv_in = make_conv_nd(
615
+ dims,
616
+ in_channels,
617
+ output_channel,
618
+ kernel_size=3,
619
+ stride=1,
620
+ padding=1,
621
+ causal=True,
622
+ spatial_padding_mode=spatial_padding_mode,
623
+ )
624
+
625
+ self.up_blocks = nn.ModuleList([])
626
+
627
+ for block_name, block_params in list(reversed(blocks)):
628
+ input_channel = output_channel
629
+ if isinstance(block_params, int):
630
+ block_params = {"num_layers": block_params}
631
+
632
+ if block_name == "res_x":
633
+ block = UNetMidBlock3D(
634
+ dims=dims,
635
+ in_channels=input_channel,
636
+ num_layers=block_params["num_layers"],
637
+ resnet_eps=1e-6,
638
+ resnet_groups=norm_num_groups,
639
+ norm_layer=norm_layer,
640
+ inject_noise=block_params.get("inject_noise", False),
641
+ timestep_conditioning=timestep_conditioning,
642
+ spatial_padding_mode=spatial_padding_mode,
643
+ )
644
+ elif block_name == "attn_res_x":
645
+ block = UNetMidBlock3D(
646
+ dims=dims,
647
+ in_channels=input_channel,
648
+ num_layers=block_params["num_layers"],
649
+ resnet_groups=norm_num_groups,
650
+ norm_layer=norm_layer,
651
+ inject_noise=block_params.get("inject_noise", False),
652
+ timestep_conditioning=timestep_conditioning,
653
+ attention_head_dim=block_params["attention_head_dim"],
654
+ spatial_padding_mode=spatial_padding_mode,
655
+ )
656
+ elif block_name == "res_x_y":
657
+ output_channel = output_channel // block_params.get("multiplier", 2)
658
+ block = ResnetBlock3D(
659
+ dims=dims,
660
+ in_channels=input_channel,
661
+ out_channels=output_channel,
662
+ eps=1e-6,
663
+ groups=norm_num_groups,
664
+ norm_layer=norm_layer,
665
+ inject_noise=block_params.get("inject_noise", False),
666
+ timestep_conditioning=False,
667
+ spatial_padding_mode=spatial_padding_mode,
668
+ )
669
+ elif block_name == "compress_time":
670
+ block = DepthToSpaceUpsample(
671
+ dims=dims,
672
+ in_channels=input_channel,
673
+ stride=(2, 1, 1),
674
+ spatial_padding_mode=spatial_padding_mode,
675
+ )
676
+ elif block_name == "compress_space":
677
+ block = DepthToSpaceUpsample(
678
+ dims=dims,
679
+ in_channels=input_channel,
680
+ stride=(1, 2, 2),
681
+ spatial_padding_mode=spatial_padding_mode,
682
+ )
683
+ elif block_name == "compress_all":
684
+ output_channel = output_channel // block_params.get("multiplier", 1)
685
+ block = DepthToSpaceUpsample(
686
+ dims=dims,
687
+ in_channels=input_channel,
688
+ stride=(2, 2, 2),
689
+ residual=block_params.get("residual", False),
690
+ out_channels_reduction_factor=block_params.get("multiplier", 1),
691
+ spatial_padding_mode=spatial_padding_mode,
692
+ )
693
+ else:
694
+ raise ValueError(f"unknown layer: {block_name}")
695
+
696
+ self.up_blocks.append(block)
697
+
698
+ if norm_layer == "group_norm":
699
+ self.conv_norm_out = nn.GroupNorm(
700
+ num_channels=output_channel, num_groups=norm_num_groups, eps=1e-6
701
+ )
702
+ elif norm_layer == "pixel_norm":
703
+ self.conv_norm_out = PixelNorm()
704
+ elif norm_layer == "layer_norm":
705
+ self.conv_norm_out = LayerNorm(output_channel, eps=1e-6)
706
+
707
+ self.conv_act = nn.SiLU()
708
+ self.conv_out = make_conv_nd(
709
+ dims,
710
+ output_channel,
711
+ out_channels,
712
+ 3,
713
+ padding=1,
714
+ causal=True,
715
+ spatial_padding_mode=spatial_padding_mode,
716
+ )
717
+
718
+ self.gradient_checkpointing = False
719
+
720
+ self.timestep_conditioning = timestep_conditioning
721
+
722
+ if timestep_conditioning:
723
+ self.timestep_scale_multiplier = nn.Parameter(
724
+ torch.tensor(1000.0, dtype=torch.float32)
725
+ )
726
+ self.last_time_embedder = PixArtAlphaCombinedTimestepSizeEmbeddings(
727
+ output_channel * 2, 0
728
+ )
729
+ self.last_scale_shift_table = nn.Parameter(
730
+ torch.randn(2, output_channel) / output_channel**0.5
731
+ )
732
+
733
+ def forward(
734
+ self,
735
+ sample: torch.FloatTensor,
736
+ target_shape,
737
+ timestep: Optional[torch.Tensor] = None,
738
+ ) -> torch.FloatTensor:
739
+ r"""The forward method of the `Decoder` class."""
740
+ assert target_shape is not None, "target_shape must be provided"
741
+ batch_size = sample.shape[0]
742
+
743
+ sample = self.conv_in(sample, causal=self.causal)
744
+
745
+ upscale_dtype = next(iter(self.up_blocks.parameters())).dtype
746
+
747
+ checkpoint_fn = (
748
+ partial(torch.utils.checkpoint.checkpoint, use_reentrant=False)
749
+ if self.gradient_checkpointing and self.training
750
+ else lambda x: x
751
+ )
752
+
753
+ sample = sample.to(upscale_dtype)
754
+
755
+ if self.timestep_conditioning:
756
+ assert (
757
+ timestep is not None
758
+ ), "should pass timestep with timestep_conditioning=True"
759
+ scaled_timestep = timestep * self.timestep_scale_multiplier
760
+
761
+ for up_block in self.up_blocks:
762
+ if self.timestep_conditioning and isinstance(up_block, UNetMidBlock3D):
763
+ sample = checkpoint_fn(up_block)(
764
+ sample, causal=self.causal, timestep=scaled_timestep
765
+ )
766
+ else:
767
+ sample = checkpoint_fn(up_block)(sample, causal=self.causal)
768
+
769
+ sample = self.conv_norm_out(sample)
770
+
771
+ if self.timestep_conditioning:
772
+ embedded_timestep = self.last_time_embedder(
773
+ timestep=scaled_timestep.flatten(),
774
+ resolution=None,
775
+ aspect_ratio=None,
776
+ batch_size=sample.shape[0],
777
+ hidden_dtype=sample.dtype,
778
+ )
779
+ embedded_timestep = embedded_timestep.view(
780
+ batch_size, embedded_timestep.shape[-1], 1, 1, 1
781
+ )
782
+ ada_values = self.last_scale_shift_table[
783
+ None, ..., None, None, None
784
+ ] + embedded_timestep.reshape(
785
+ batch_size,
786
+ 2,
787
+ -1,
788
+ embedded_timestep.shape[-3],
789
+ embedded_timestep.shape[-2],
790
+ embedded_timestep.shape[-1],
791
+ )
792
+ shift, scale = ada_values.unbind(dim=1)
793
+ sample = sample * (1 + scale) + shift
794
+
795
+ sample = self.conv_act(sample)
796
+ sample = self.conv_out(sample, causal=self.causal)
797
+
798
+ sample = unpatchify(sample, patch_size_hw=self.patch_size, patch_size_t=1)
799
+
800
+ return sample
801
+
802
+
803
+ class UNetMidBlock3D(nn.Module):
804
+ """
805
+ A 3D UNet mid-block [`UNetMidBlock3D`] with multiple residual blocks.
806
+
807
+ Args:
808
+ in_channels (`int`): The number of input channels.
809
+ dropout (`float`, *optional*, defaults to 0.0): The dropout rate.
810
+ num_layers (`int`, *optional*, defaults to 1): The number of residual blocks.
811
+ resnet_eps (`float`, *optional*, 1e-6 ): The epsilon value for the resnet blocks.
812
+ resnet_groups (`int`, *optional*, defaults to 32):
813
+ The number of groups to use in the group normalization layers of the resnet blocks.
814
+ norm_layer (`str`, *optional*, defaults to `group_norm`):
815
+ The normalization layer to use. Can be either `group_norm` or `pixel_norm`.
816
+ inject_noise (`bool`, *optional*, defaults to `False`):
817
+ Whether to inject noise into the hidden states.
818
+ timestep_conditioning (`bool`, *optional*, defaults to `False`):
819
+ Whether to condition the hidden states on the timestep.
820
+ attention_head_dim (`int`, *optional*, defaults to -1):
821
+ The dimension of the attention head. If -1, no attention is used.
822
+
823
+ Returns:
824
+ `torch.FloatTensor`: The output of the last residual block, which is a tensor of shape `(batch_size,
825
+ in_channels, height, width)`.
826
+
827
+ """
828
+
829
+ def __init__(
830
+ self,
831
+ dims: Union[int, Tuple[int, int]],
832
+ in_channels: int,
833
+ dropout: float = 0.0,
834
+ num_layers: int = 1,
835
+ resnet_eps: float = 1e-6,
836
+ resnet_groups: int = 32,
837
+ norm_layer: str = "group_norm",
838
+ inject_noise: bool = False,
839
+ timestep_conditioning: bool = False,
840
+ attention_head_dim: int = -1,
841
+ spatial_padding_mode: str = "zeros",
842
+ ):
843
+ super().__init__()
844
+ resnet_groups = (
845
+ resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
846
+ )
847
+ self.timestep_conditioning = timestep_conditioning
848
+
849
+ if timestep_conditioning:
850
+ self.time_embedder = PixArtAlphaCombinedTimestepSizeEmbeddings(
851
+ in_channels * 4, 0
852
+ )
853
+
854
+ self.res_blocks = nn.ModuleList(
855
+ [
856
+ ResnetBlock3D(
857
+ dims=dims,
858
+ in_channels=in_channels,
859
+ out_channels=in_channels,
860
+ eps=resnet_eps,
861
+ groups=resnet_groups,
862
+ dropout=dropout,
863
+ norm_layer=norm_layer,
864
+ inject_noise=inject_noise,
865
+ timestep_conditioning=timestep_conditioning,
866
+ spatial_padding_mode=spatial_padding_mode,
867
+ )
868
+ for _ in range(num_layers)
869
+ ]
870
+ )
871
+
872
+ self.attention_blocks = None
873
+
874
+ if attention_head_dim > 0:
875
+ if attention_head_dim > in_channels:
876
+ raise ValueError(
877
+ "attention_head_dim must be less than or equal to in_channels"
878
+ )
879
+
880
+ self.attention_blocks = nn.ModuleList(
881
+ [
882
+ Attention(
883
+ query_dim=in_channels,
884
+ heads=in_channels // attention_head_dim,
885
+ dim_head=attention_head_dim,
886
+ bias=True,
887
+ out_bias=True,
888
+ qk_norm="rms_norm",
889
+ residual_connection=True,
890
+ )
891
+ for _ in range(num_layers)
892
+ ]
893
+ )
894
+
895
+ def forward(
896
+ self,
897
+ hidden_states: torch.FloatTensor,
898
+ causal: bool = True,
899
+ timestep: Optional[torch.Tensor] = None,
900
+ ) -> torch.FloatTensor:
901
+ timestep_embed = None
902
+ if self.timestep_conditioning:
903
+ assert (
904
+ timestep is not None
905
+ ), "should pass timestep with timestep_conditioning=True"
906
+ batch_size = hidden_states.shape[0]
907
+ timestep_embed = self.time_embedder(
908
+ timestep=timestep.flatten(),
909
+ resolution=None,
910
+ aspect_ratio=None,
911
+ batch_size=batch_size,
912
+ hidden_dtype=hidden_states.dtype,
913
+ )
914
+ timestep_embed = timestep_embed.view(
915
+ batch_size, timestep_embed.shape[-1], 1, 1, 1
916
+ )
917
+
918
+ if self.attention_blocks:
919
+ for resnet, attention in zip(self.res_blocks, self.attention_blocks):
920
+ hidden_states = resnet(
921
+ hidden_states, causal=causal, timestep=timestep_embed
922
+ )
923
+
924
+ # Reshape the hidden states to be (batch_size, frames * height * width, channel)
925
+ batch_size, channel, frames, height, width = hidden_states.shape
926
+ hidden_states = hidden_states.view(
927
+ batch_size, channel, frames * height * width
928
+ ).transpose(1, 2)
929
+
930
+ if attention.use_tpu_flash_attention:
931
+ # Pad the second dimension to be divisible by block_k_major (block in flash attention)
932
+ seq_len = hidden_states.shape[1]
933
+ block_k_major = 512
934
+ pad_len = (block_k_major - seq_len % block_k_major) % block_k_major
935
+ if pad_len > 0:
936
+ hidden_states = F.pad(
937
+ hidden_states, (0, 0, 0, pad_len), "constant", 0
938
+ )
939
+
940
+ # Create a mask with ones for the original sequence length and zeros for the padded indexes
941
+ mask = torch.ones(
942
+ (hidden_states.shape[0], seq_len),
943
+ device=hidden_states.device,
944
+ dtype=hidden_states.dtype,
945
+ )
946
+ if pad_len > 0:
947
+ mask = F.pad(mask, (0, pad_len), "constant", 0)
948
+
949
+ hidden_states = attention(
950
+ hidden_states,
951
+ attention_mask=(
952
+ None if not attention.use_tpu_flash_attention else mask
953
+ ),
954
+ )
955
+
956
+ if attention.use_tpu_flash_attention:
957
+ # Remove the padding
958
+ if pad_len > 0:
959
+ hidden_states = hidden_states[:, :-pad_len, :]
960
+
961
+ # Reshape the hidden states back to (batch_size, channel, frames, height, width, channel)
962
+ hidden_states = hidden_states.transpose(-1, -2).reshape(
963
+ batch_size, channel, frames, height, width
964
+ )
965
+ else:
966
+ for resnet in self.res_blocks:
967
+ hidden_states = resnet(
968
+ hidden_states, causal=causal, timestep=timestep_embed
969
+ )
970
+
971
+ return hidden_states
972
+
973
+
974
+ class SpaceToDepthDownsample(nn.Module):
975
+ def __init__(self, dims, in_channels, out_channels, stride, spatial_padding_mode):
976
+ super().__init__()
977
+ self.stride = stride
978
+ self.group_size = in_channels * np.prod(stride) // out_channels
979
+ self.conv = make_conv_nd(
980
+ dims=dims,
981
+ in_channels=in_channels,
982
+ out_channels=out_channels // np.prod(stride),
983
+ kernel_size=3,
984
+ stride=1,
985
+ causal=True,
986
+ spatial_padding_mode=spatial_padding_mode,
987
+ )
988
+
989
+ def forward(self, x, causal: bool = True):
990
+ if self.stride[0] == 2:
991
+ x = torch.cat(
992
+ [x[:, :, :1, :, :], x], dim=2
993
+ ) # duplicate first frames for padding
994
+
995
+ # skip connection
996
+ x_in = rearrange(
997
+ x,
998
+ "b c (d p1) (h p2) (w p3) -> b (c p1 p2 p3) d h w",
999
+ p1=self.stride[0],
1000
+ p2=self.stride[1],
1001
+ p3=self.stride[2],
1002
+ )
1003
+ x_in = rearrange(x_in, "b (c g) d h w -> b c g d h w", g=self.group_size)
1004
+ x_in = x_in.mean(dim=2)
1005
+
1006
+ # conv
1007
+ x = self.conv(x, causal=causal)
1008
+ x = rearrange(
1009
+ x,
1010
+ "b c (d p1) (h p2) (w p3) -> b (c p1 p2 p3) d h w",
1011
+ p1=self.stride[0],
1012
+ p2=self.stride[1],
1013
+ p3=self.stride[2],
1014
+ )
1015
+
1016
+ x = x + x_in
1017
+
1018
+ return x
1019
+
1020
+
1021
+ class DepthToSpaceUpsample(nn.Module):
1022
+ def __init__(
1023
+ self,
1024
+ dims,
1025
+ in_channels,
1026
+ stride,
1027
+ residual=False,
1028
+ out_channels_reduction_factor=1,
1029
+ spatial_padding_mode="zeros",
1030
+ ):
1031
+ super().__init__()
1032
+ self.stride = stride
1033
+ self.out_channels = (
1034
+ np.prod(stride) * in_channels // out_channels_reduction_factor
1035
+ )
1036
+ self.conv = make_conv_nd(
1037
+ dims=dims,
1038
+ in_channels=in_channels,
1039
+ out_channels=self.out_channels,
1040
+ kernel_size=3,
1041
+ stride=1,
1042
+ causal=True,
1043
+ spatial_padding_mode=spatial_padding_mode,
1044
+ )
1045
+ self.pixel_shuffle = PixelShuffleND(dims=dims, upscale_factors=stride)
1046
+ self.residual = residual
1047
+ self.out_channels_reduction_factor = out_channels_reduction_factor
1048
+
1049
+ def forward(self, x, causal: bool = True):
1050
+ if self.residual:
1051
+ # Reshape and duplicate the input to match the output shape
1052
+ x_in = self.pixel_shuffle(x)
1053
+ num_repeat = np.prod(self.stride) // self.out_channels_reduction_factor
1054
+ x_in = x_in.repeat(1, num_repeat, 1, 1, 1)
1055
+ if self.stride[0] == 2:
1056
+ x_in = x_in[:, :, 1:, :, :]
1057
+ x = self.conv(x, causal=causal)
1058
+ x = self.pixel_shuffle(x)
1059
+ if self.stride[0] == 2:
1060
+ x = x[:, :, 1:, :, :]
1061
+ if self.residual:
1062
+ x = x + x_in
1063
+ return x
1064
+
1065
+
1066
+ class LayerNorm(nn.Module):
1067
+ def __init__(self, dim, eps, elementwise_affine=True) -> None:
1068
+ super().__init__()
1069
+ self.norm = nn.LayerNorm(dim, eps=eps, elementwise_affine=elementwise_affine)
1070
+
1071
+ def forward(self, x):
1072
+ x = rearrange(x, "b c d h w -> b d h w c")
1073
+ x = self.norm(x)
1074
+ x = rearrange(x, "b d h w c -> b c d h w")
1075
+ return x
1076
+
1077
+
1078
+ class ResnetBlock3D(nn.Module):
1079
+ r"""
1080
+ A Resnet block.
1081
+
1082
+ Parameters:
1083
+ in_channels (`int`): The number of channels in the input.
1084
+ out_channels (`int`, *optional*, default to be `None`):
1085
+ The number of output channels for the first conv layer. If None, same as `in_channels`.
1086
+ dropout (`float`, *optional*, defaults to `0.0`): The dropout probability to use.
1087
+ groups (`int`, *optional*, default to `32`): The number of groups to use for the first normalization layer.
1088
+ eps (`float`, *optional*, defaults to `1e-6`): The epsilon to use for the normalization.
1089
+ """
1090
+
1091
+ def __init__(
1092
+ self,
1093
+ dims: Union[int, Tuple[int, int]],
1094
+ in_channels: int,
1095
+ out_channels: Optional[int] = None,
1096
+ dropout: float = 0.0,
1097
+ groups: int = 32,
1098
+ eps: float = 1e-6,
1099
+ norm_layer: str = "group_norm",
1100
+ inject_noise: bool = False,
1101
+ timestep_conditioning: bool = False,
1102
+ spatial_padding_mode: str = "zeros",
1103
+ ):
1104
+ super().__init__()
1105
+ self.in_channels = in_channels
1106
+ out_channels = in_channels if out_channels is None else out_channels
1107
+ self.out_channels = out_channels
1108
+ self.inject_noise = inject_noise
1109
+
1110
+ if norm_layer == "group_norm":
1111
+ self.norm1 = nn.GroupNorm(
1112
+ num_groups=groups, num_channels=in_channels, eps=eps, affine=True
1113
+ )
1114
+ elif norm_layer == "pixel_norm":
1115
+ self.norm1 = PixelNorm()
1116
+ elif norm_layer == "layer_norm":
1117
+ self.norm1 = LayerNorm(in_channels, eps=eps, elementwise_affine=True)
1118
+
1119
+ self.non_linearity = nn.SiLU()
1120
+
1121
+ self.conv1 = make_conv_nd(
1122
+ dims,
1123
+ in_channels,
1124
+ out_channels,
1125
+ kernel_size=3,
1126
+ stride=1,
1127
+ padding=1,
1128
+ causal=True,
1129
+ spatial_padding_mode=spatial_padding_mode,
1130
+ )
1131
+
1132
+ if inject_noise:
1133
+ self.per_channel_scale1 = nn.Parameter(torch.zeros((in_channels, 1, 1)))
1134
+
1135
+ if norm_layer == "group_norm":
1136
+ self.norm2 = nn.GroupNorm(
1137
+ num_groups=groups, num_channels=out_channels, eps=eps, affine=True
1138
+ )
1139
+ elif norm_layer == "pixel_norm":
1140
+ self.norm2 = PixelNorm()
1141
+ elif norm_layer == "layer_norm":
1142
+ self.norm2 = LayerNorm(out_channels, eps=eps, elementwise_affine=True)
1143
+
1144
+ self.dropout = torch.nn.Dropout(dropout)
1145
+
1146
+ self.conv2 = make_conv_nd(
1147
+ dims,
1148
+ out_channels,
1149
+ out_channels,
1150
+ kernel_size=3,
1151
+ stride=1,
1152
+ padding=1,
1153
+ causal=True,
1154
+ spatial_padding_mode=spatial_padding_mode,
1155
+ )
1156
+
1157
+ if inject_noise:
1158
+ self.per_channel_scale2 = nn.Parameter(torch.zeros((in_channels, 1, 1)))
1159
+
1160
+ self.conv_shortcut = (
1161
+ make_linear_nd(
1162
+ dims=dims, in_channels=in_channels, out_channels=out_channels
1163
+ )
1164
+ if in_channels != out_channels
1165
+ else nn.Identity()
1166
+ )
1167
+
1168
+ self.norm3 = (
1169
+ LayerNorm(in_channels, eps=eps, elementwise_affine=True)
1170
+ if in_channels != out_channels
1171
+ else nn.Identity()
1172
+ )
1173
+
1174
+ self.timestep_conditioning = timestep_conditioning
1175
+
1176
+ if timestep_conditioning:
1177
+ self.scale_shift_table = nn.Parameter(
1178
+ torch.randn(4, in_channels) / in_channels**0.5
1179
+ )
1180
+
1181
+ def _feed_spatial_noise(
1182
+ self, hidden_states: torch.FloatTensor, per_channel_scale: torch.FloatTensor
1183
+ ) -> torch.FloatTensor:
1184
+ spatial_shape = hidden_states.shape[-2:]
1185
+ device = hidden_states.device
1186
+ dtype = hidden_states.dtype
1187
+
1188
+ # similar to the "explicit noise inputs" method in style-gan
1189
+ spatial_noise = torch.randn(spatial_shape, device=device, dtype=dtype)[None]
1190
+ scaled_noise = (spatial_noise * per_channel_scale)[None, :, None, ...]
1191
+ hidden_states = hidden_states + scaled_noise
1192
+
1193
+ return hidden_states
1194
+
1195
+ def forward(
1196
+ self,
1197
+ input_tensor: torch.FloatTensor,
1198
+ causal: bool = True,
1199
+ timestep: Optional[torch.Tensor] = None,
1200
+ ) -> torch.FloatTensor:
1201
+ hidden_states = input_tensor
1202
+ batch_size = hidden_states.shape[0]
1203
+
1204
+ hidden_states = self.norm1(hidden_states)
1205
+ if self.timestep_conditioning:
1206
+ assert (
1207
+ timestep is not None
1208
+ ), "should pass timestep with timestep_conditioning=True"
1209
+ ada_values = self.scale_shift_table[
1210
+ None, ..., None, None, None
1211
+ ] + timestep.reshape(
1212
+ batch_size,
1213
+ 4,
1214
+ -1,
1215
+ timestep.shape[-3],
1216
+ timestep.shape[-2],
1217
+ timestep.shape[-1],
1218
+ )
1219
+ shift1, scale1, shift2, scale2 = ada_values.unbind(dim=1)
1220
+
1221
+ hidden_states = hidden_states * (1 + scale1) + shift1
1222
+
1223
+ hidden_states = self.non_linearity(hidden_states)
1224
+
1225
+ hidden_states = self.conv1(hidden_states, causal=causal)
1226
+
1227
+ if self.inject_noise:
1228
+ hidden_states = self._feed_spatial_noise(
1229
+ hidden_states, self.per_channel_scale1
1230
+ )
1231
+
1232
+ hidden_states = self.norm2(hidden_states)
1233
+
1234
+ if self.timestep_conditioning:
1235
+ hidden_states = hidden_states * (1 + scale2) + shift2
1236
+
1237
+ hidden_states = self.non_linearity(hidden_states)
1238
+
1239
+ hidden_states = self.dropout(hidden_states)
1240
+
1241
+ hidden_states = self.conv2(hidden_states, causal=causal)
1242
+
1243
+ if self.inject_noise:
1244
+ hidden_states = self._feed_spatial_noise(
1245
+ hidden_states, self.per_channel_scale2
1246
+ )
1247
+
1248
+ input_tensor = self.norm3(input_tensor)
1249
+
1250
+ batch_size = input_tensor.shape[0]
1251
+
1252
+ input_tensor = self.conv_shortcut(input_tensor)
1253
+
1254
+ output_tensor = input_tensor + hidden_states
1255
+
1256
+ return output_tensor
1257
+
1258
+
1259
+ def patchify(x, patch_size_hw, patch_size_t=1):
1260
+ if patch_size_hw == 1 and patch_size_t == 1:
1261
+ return x
1262
+ if x.dim() == 4:
1263
+ x = rearrange(
1264
+ x, "b c (h q) (w r) -> b (c r q) h w", q=patch_size_hw, r=patch_size_hw
1265
+ )
1266
+ elif x.dim() == 5:
1267
+ x = rearrange(
1268
+ x,
1269
+ "b c (f p) (h q) (w r) -> b (c p r q) f h w",
1270
+ p=patch_size_t,
1271
+ q=patch_size_hw,
1272
+ r=patch_size_hw,
1273
+ )
1274
+ else:
1275
+ raise ValueError(f"Invalid input shape: {x.shape}")
1276
+
1277
+ return x
1278
+
1279
+
1280
+ def unpatchify(x, patch_size_hw, patch_size_t=1):
1281
+ if patch_size_hw == 1 and patch_size_t == 1:
1282
+ return x
1283
+
1284
+ if x.dim() == 4:
1285
+ x = rearrange(
1286
+ x, "b (c r q) h w -> b c (h q) (w r)", q=patch_size_hw, r=patch_size_hw
1287
+ )
1288
+ elif x.dim() == 5:
1289
+ x = rearrange(
1290
+ x,
1291
+ "b (c p r q) f h w -> b c (f p) (h q) (w r)",
1292
+ p=patch_size_t,
1293
+ q=patch_size_hw,
1294
+ r=patch_size_hw,
1295
+ )
1296
+
1297
+ return x
1298
+
1299
+
1300
+ def create_video_autoencoder_demo_config(
1301
+ latent_channels: int = 64,
1302
+ ):
1303
+ encoder_blocks = [
1304
+ ("res_x", {"num_layers": 2}),
1305
+ ("compress_space_res", {"multiplier": 2}),
1306
+ ("res_x", {"num_layers": 2}),
1307
+ ("compress_time_res", {"multiplier": 2}),
1308
+ ("res_x", {"num_layers": 1}),
1309
+ ("compress_all_res", {"multiplier": 2}),
1310
+ ("res_x", {"num_layers": 1}),
1311
+ ("compress_all_res", {"multiplier": 2}),
1312
+ ("res_x", {"num_layers": 1}),
1313
+ ]
1314
+ decoder_blocks = [
1315
+ ("res_x", {"num_layers": 2, "inject_noise": False}),
1316
+ ("compress_all", {"residual": True, "multiplier": 2}),
1317
+ ("res_x", {"num_layers": 2, "inject_noise": False}),
1318
+ ("compress_all", {"residual": True, "multiplier": 2}),
1319
+ ("res_x", {"num_layers": 2, "inject_noise": False}),
1320
+ ("compress_all", {"residual": True, "multiplier": 2}),
1321
+ ("res_x", {"num_layers": 2, "inject_noise": False}),
1322
+ ]
1323
+ return {
1324
+ "_class_name": "CausalVideoAutoencoder",
1325
+ "dims": 3,
1326
+ "encoder_blocks": encoder_blocks,
1327
+ "decoder_blocks": decoder_blocks,
1328
+ "latent_channels": latent_channels,
1329
+ "norm_layer": "pixel_norm",
1330
+ "patch_size": 4,
1331
+ "latent_log_var": "uniform",
1332
+ "use_quant_conv": False,
1333
+ "causal_decoder": False,
1334
+ "timestep_conditioning": True,
1335
+ "spatial_padding_mode": "replicate",
1336
+ }
1337
+
1338
+
1339
+ def test_vae_patchify_unpatchify():
1340
+ import torch
1341
+
1342
+ x = torch.randn(2, 3, 8, 64, 64)
1343
+ x_patched = patchify(x, patch_size_hw=4, patch_size_t=4)
1344
+ x_unpatched = unpatchify(x_patched, patch_size_hw=4, patch_size_t=4)
1345
+ assert torch.allclose(x, x_unpatched)
1346
+
1347
+
1348
+ def demo_video_autoencoder_forward_backward():
1349
+ # Configuration for the VideoAutoencoder
1350
+ config = create_video_autoencoder_demo_config()
1351
+
1352
+ # Instantiate the VideoAutoencoder with the specified configuration
1353
+ video_autoencoder = CausalVideoAutoencoder.from_config(config)
1354
+
1355
+ print(video_autoencoder)
1356
+ video_autoencoder.eval()
1357
+ # Print the total number of parameters in the video autoencoder
1358
+ total_params = sum(p.numel() for p in video_autoencoder.parameters())
1359
+ print(f"Total number of parameters in VideoAutoencoder: {total_params:,}")
1360
+
1361
+ # Create a mock input tensor simulating a batch of videos
1362
+ # Shape: (batch_size, channels, depth, height, width)
1363
+ # E.g., 4 videos, each with 3 color channels, 16 frames, and 64x64 pixels per frame
1364
+ input_videos = torch.randn(2, 3, 17, 64, 64)
1365
+
1366
+ # Forward pass: encode and decode the input videos
1367
+ latent = video_autoencoder.encode(input_videos).latent_dist.mode()
1368
+ print(f"input shape={input_videos.shape}")
1369
+ print(f"latent shape={latent.shape}")
1370
+
1371
+ timestep = torch.ones(input_videos.shape[0]) * 0.1
1372
+ reconstructed_videos = video_autoencoder.decode(
1373
+ latent, target_shape=input_videos.shape, timestep=timestep
1374
+ ).sample
1375
+
1376
+ print(f"reconstructed shape={reconstructed_videos.shape}")
1377
+
1378
+ # Validate that single image gets treated the same way as first frame
1379
+ input_image = input_videos[:, :, :1, :, :]
1380
+ image_latent = video_autoencoder.encode(input_image).latent_dist.mode()
1381
+ _ = video_autoencoder.decode(
1382
+ image_latent, target_shape=image_latent.shape, timestep=timestep
1383
+ ).sample
1384
+
1385
+ first_frame_latent = latent[:, :, :1, :, :]
1386
+
1387
+ assert torch.allclose(image_latent, first_frame_latent, atol=1e-6)
1388
+ # assert torch.allclose(reconstructed_image, reconstructed_videos[:, :, :1, :, :], atol=1e-6)
1389
+ # assert torch.allclose(image_latent, first_frame_latent, atol=1e-6)
1390
+ # assert (reconstructed_image == reconstructed_videos[:, :, :1, :, :]).all()
1391
+
1392
+ # Calculate the loss (e.g., mean squared error)
1393
+ loss = torch.nn.functional.mse_loss(input_videos, reconstructed_videos)
1394
+
1395
+ # Perform backward pass
1396
+ loss.backward()
1397
+
1398
+ print(f"Demo completed with loss: {loss.item()}")
1399
+
1400
+
1401
+ # Ensure to call the demo function to execute the forward and backward pass
1402
+ if __name__ == "__main__":
1403
+ demo_video_autoencoder_forward_backward()
ltx_video/models/autoencoders/conv_nd_factory.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple, Union
2
+
3
+ import torch
4
+
5
+ from ltx_video.models.autoencoders.dual_conv3d import DualConv3d
6
+ from ltx_video.models.autoencoders.causal_conv3d import CausalConv3d
7
+
8
+
9
+ def make_conv_nd(
10
+ dims: Union[int, Tuple[int, int]],
11
+ in_channels: int,
12
+ out_channels: int,
13
+ kernel_size: int,
14
+ stride=1,
15
+ padding=0,
16
+ dilation=1,
17
+ groups=1,
18
+ bias=True,
19
+ causal=False,
20
+ spatial_padding_mode="zeros",
21
+ temporal_padding_mode="zeros",
22
+ ):
23
+ if not (spatial_padding_mode == temporal_padding_mode or causal):
24
+ raise NotImplementedError("spatial and temporal padding modes must be equal")
25
+ if dims == 2:
26
+ return torch.nn.Conv2d(
27
+ in_channels=in_channels,
28
+ out_channels=out_channels,
29
+ kernel_size=kernel_size,
30
+ stride=stride,
31
+ padding=padding,
32
+ dilation=dilation,
33
+ groups=groups,
34
+ bias=bias,
35
+ padding_mode=spatial_padding_mode,
36
+ )
37
+ elif dims == 3:
38
+ if causal:
39
+ return CausalConv3d(
40
+ in_channels=in_channels,
41
+ out_channels=out_channels,
42
+ kernel_size=kernel_size,
43
+ stride=stride,
44
+ padding=padding,
45
+ dilation=dilation,
46
+ groups=groups,
47
+ bias=bias,
48
+ spatial_padding_mode=spatial_padding_mode,
49
+ )
50
+ return torch.nn.Conv3d(
51
+ in_channels=in_channels,
52
+ out_channels=out_channels,
53
+ kernel_size=kernel_size,
54
+ stride=stride,
55
+ padding=padding,
56
+ dilation=dilation,
57
+ groups=groups,
58
+ bias=bias,
59
+ padding_mode=spatial_padding_mode,
60
+ )
61
+ elif dims == (2, 1):
62
+ return DualConv3d(
63
+ in_channels=in_channels,
64
+ out_channels=out_channels,
65
+ kernel_size=kernel_size,
66
+ stride=stride,
67
+ padding=padding,
68
+ bias=bias,
69
+ padding_mode=spatial_padding_mode,
70
+ )
71
+ else:
72
+ raise ValueError(f"unsupported dimensions: {dims}")
73
+
74
+
75
+ def make_linear_nd(
76
+ dims: int,
77
+ in_channels: int,
78
+ out_channels: int,
79
+ bias=True,
80
+ ):
81
+ if dims == 2:
82
+ return torch.nn.Conv2d(
83
+ in_channels=in_channels, out_channels=out_channels, kernel_size=1, bias=bias
84
+ )
85
+ elif dims == 3 or dims == (2, 1):
86
+ return torch.nn.Conv3d(
87
+ in_channels=in_channels, out_channels=out_channels, kernel_size=1, bias=bias
88
+ )
89
+ else:
90
+ raise ValueError(f"unsupported dimensions: {dims}")
ltx_video/models/autoencoders/dual_conv3d.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Tuple, Union
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from einops import rearrange
8
+
9
+
10
+ class DualConv3d(nn.Module):
11
+ def __init__(
12
+ self,
13
+ in_channels,
14
+ out_channels,
15
+ kernel_size,
16
+ stride: Union[int, Tuple[int, int, int]] = 1,
17
+ padding: Union[int, Tuple[int, int, int]] = 0,
18
+ dilation: Union[int, Tuple[int, int, int]] = 1,
19
+ groups=1,
20
+ bias=True,
21
+ padding_mode="zeros",
22
+ ):
23
+ super(DualConv3d, self).__init__()
24
+
25
+ self.in_channels = in_channels
26
+ self.out_channels = out_channels
27
+ self.padding_mode = padding_mode
28
+ # Ensure kernel_size, stride, padding, and dilation are tuples of length 3
29
+ if isinstance(kernel_size, int):
30
+ kernel_size = (kernel_size, kernel_size, kernel_size)
31
+ if kernel_size == (1, 1, 1):
32
+ raise ValueError(
33
+ "kernel_size must be greater than 1. Use make_linear_nd instead."
34
+ )
35
+ if isinstance(stride, int):
36
+ stride = (stride, stride, stride)
37
+ if isinstance(padding, int):
38
+ padding = (padding, padding, padding)
39
+ if isinstance(dilation, int):
40
+ dilation = (dilation, dilation, dilation)
41
+
42
+ # Set parameters for convolutions
43
+ self.groups = groups
44
+ self.bias = bias
45
+
46
+ # Define the size of the channels after the first convolution
47
+ intermediate_channels = (
48
+ out_channels if in_channels < out_channels else in_channels
49
+ )
50
+
51
+ # Define parameters for the first convolution
52
+ self.weight1 = nn.Parameter(
53
+ torch.Tensor(
54
+ intermediate_channels,
55
+ in_channels // groups,
56
+ 1,
57
+ kernel_size[1],
58
+ kernel_size[2],
59
+ )
60
+ )
61
+ self.stride1 = (1, stride[1], stride[2])
62
+ self.padding1 = (0, padding[1], padding[2])
63
+ self.dilation1 = (1, dilation[1], dilation[2])
64
+ if bias:
65
+ self.bias1 = nn.Parameter(torch.Tensor(intermediate_channels))
66
+ else:
67
+ self.register_parameter("bias1", None)
68
+
69
+ # Define parameters for the second convolution
70
+ self.weight2 = nn.Parameter(
71
+ torch.Tensor(
72
+ out_channels, intermediate_channels // groups, kernel_size[0], 1, 1
73
+ )
74
+ )
75
+ self.stride2 = (stride[0], 1, 1)
76
+ self.padding2 = (padding[0], 0, 0)
77
+ self.dilation2 = (dilation[0], 1, 1)
78
+ if bias:
79
+ self.bias2 = nn.Parameter(torch.Tensor(out_channels))
80
+ else:
81
+ self.register_parameter("bias2", None)
82
+
83
+ # Initialize weights and biases
84
+ self.reset_parameters()
85
+
86
+ def reset_parameters(self):
87
+ nn.init.kaiming_uniform_(self.weight1, a=math.sqrt(5))
88
+ nn.init.kaiming_uniform_(self.weight2, a=math.sqrt(5))
89
+ if self.bias:
90
+ fan_in1, _ = nn.init._calculate_fan_in_and_fan_out(self.weight1)
91
+ bound1 = 1 / math.sqrt(fan_in1)
92
+ nn.init.uniform_(self.bias1, -bound1, bound1)
93
+ fan_in2, _ = nn.init._calculate_fan_in_and_fan_out(self.weight2)
94
+ bound2 = 1 / math.sqrt(fan_in2)
95
+ nn.init.uniform_(self.bias2, -bound2, bound2)
96
+
97
+ def forward(self, x, use_conv3d=False, skip_time_conv=False):
98
+ if use_conv3d:
99
+ return self.forward_with_3d(x=x, skip_time_conv=skip_time_conv)
100
+ else:
101
+ return self.forward_with_2d(x=x, skip_time_conv=skip_time_conv)
102
+
103
+ def forward_with_3d(self, x, skip_time_conv):
104
+ # First convolution
105
+ x = F.conv3d(
106
+ x,
107
+ self.weight1,
108
+ self.bias1,
109
+ self.stride1,
110
+ self.padding1,
111
+ self.dilation1,
112
+ self.groups,
113
+ padding_mode=self.padding_mode,
114
+ )
115
+
116
+ if skip_time_conv:
117
+ return x
118
+
119
+ # Second convolution
120
+ x = F.conv3d(
121
+ x,
122
+ self.weight2,
123
+ self.bias2,
124
+ self.stride2,
125
+ self.padding2,
126
+ self.dilation2,
127
+ self.groups,
128
+ padding_mode=self.padding_mode,
129
+ )
130
+
131
+ return x
132
+
133
+ def forward_with_2d(self, x, skip_time_conv):
134
+ b, c, d, h, w = x.shape
135
+
136
+ # First 2D convolution
137
+ x = rearrange(x, "b c d h w -> (b d) c h w")
138
+ # Squeeze the depth dimension out of weight1 since it's 1
139
+ weight1 = self.weight1.squeeze(2)
140
+ # Select stride, padding, and dilation for the 2D convolution
141
+ stride1 = (self.stride1[1], self.stride1[2])
142
+ padding1 = (self.padding1[1], self.padding1[2])
143
+ dilation1 = (self.dilation1[1], self.dilation1[2])
144
+ x = F.conv2d(
145
+ x,
146
+ weight1,
147
+ self.bias1,
148
+ stride1,
149
+ padding1,
150
+ dilation1,
151
+ self.groups,
152
+ padding_mode=self.padding_mode,
153
+ )
154
+
155
+ _, _, h, w = x.shape
156
+
157
+ if skip_time_conv:
158
+ x = rearrange(x, "(b d) c h w -> b c d h w", b=b)
159
+ return x
160
+
161
+ # Second convolution which is essentially treated as a 1D convolution across the 'd' dimension
162
+ x = rearrange(x, "(b d) c h w -> (b h w) c d", b=b)
163
+
164
+ # Reshape weight2 to match the expected dimensions for conv1d
165
+ weight2 = self.weight2.squeeze(-1).squeeze(-1)
166
+ # Use only the relevant dimension for stride, padding, and dilation for the 1D convolution
167
+ stride2 = self.stride2[0]
168
+ padding2 = self.padding2[0]
169
+ dilation2 = self.dilation2[0]
170
+ x = F.conv1d(
171
+ x,
172
+ weight2,
173
+ self.bias2,
174
+ stride2,
175
+ padding2,
176
+ dilation2,
177
+ self.groups,
178
+ padding_mode=self.padding_mode,
179
+ )
180
+ x = rearrange(x, "(b h w) c d -> b c d h w", b=b, h=h, w=w)
181
+
182
+ return x
183
+
184
+ @property
185
+ def weight(self):
186
+ return self.weight2
187
+
188
+
189
+ def test_dual_conv3d_consistency():
190
+ # Initialize parameters
191
+ in_channels = 3
192
+ out_channels = 5
193
+ kernel_size = (3, 3, 3)
194
+ stride = (2, 2, 2)
195
+ padding = (1, 1, 1)
196
+
197
+ # Create an instance of the DualConv3d class
198
+ dual_conv3d = DualConv3d(
199
+ in_channels=in_channels,
200
+ out_channels=out_channels,
201
+ kernel_size=kernel_size,
202
+ stride=stride,
203
+ padding=padding,
204
+ bias=True,
205
+ )
206
+
207
+ # Example input tensor
208
+ test_input = torch.randn(1, 3, 10, 10, 10)
209
+
210
+ # Perform forward passes with both 3D and 2D settings
211
+ output_conv3d = dual_conv3d(test_input, use_conv3d=True)
212
+ output_2d = dual_conv3d(test_input, use_conv3d=False)
213
+
214
+ # Assert that the outputs from both methods are sufficiently close
215
+ assert torch.allclose(
216
+ output_conv3d, output_2d, atol=1e-6
217
+ ), "Outputs are not consistent between 3D and 2D convolutions."
ltx_video/models/autoencoders/latent_upsampler.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union
2
+ from pathlib import Path
3
+ import os
4
+ import json
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ from einops import rearrange
9
+ from diffusers import ConfigMixin, ModelMixin
10
+ from safetensors.torch import safe_open
11
+
12
+ from ltx_video.models.autoencoders.pixel_shuffle import PixelShuffleND
13
+
14
+
15
+ class ResBlock(nn.Module):
16
+ def __init__(
17
+ self, channels: int, mid_channels: Optional[int] = None, dims: int = 3
18
+ ):
19
+ super().__init__()
20
+ if mid_channels is None:
21
+ mid_channels = channels
22
+
23
+ Conv = nn.Conv2d if dims == 2 else nn.Conv3d
24
+
25
+ self.conv1 = Conv(channels, mid_channels, kernel_size=3, padding=1)
26
+ self.norm1 = nn.GroupNorm(32, mid_channels)
27
+ self.conv2 = Conv(mid_channels, channels, kernel_size=3, padding=1)
28
+ self.norm2 = nn.GroupNorm(32, channels)
29
+ self.activation = nn.SiLU()
30
+
31
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
32
+ residual = x
33
+ x = self.conv1(x)
34
+ x = self.norm1(x)
35
+ x = self.activation(x)
36
+ x = self.conv2(x)
37
+ x = self.norm2(x)
38
+ x = self.activation(x + residual)
39
+ return x
40
+
41
+
42
+ class LatentUpsampler(ModelMixin, ConfigMixin):
43
+ """
44
+ Model to spatially upsample VAE latents.
45
+
46
+ Args:
47
+ in_channels (`int`): Number of channels in the input latent
48
+ mid_channels (`int`): Number of channels in the middle layers
49
+ num_blocks_per_stage (`int`): Number of ResBlocks to use in each stage (pre/post upsampling)
50
+ dims (`int`): Number of dimensions for convolutions (2 or 3)
51
+ spatial_upsample (`bool`): Whether to spatially upsample the latent
52
+ temporal_upsample (`bool`): Whether to temporally upsample the latent
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ in_channels: int = 128,
58
+ mid_channels: int = 512,
59
+ num_blocks_per_stage: int = 4,
60
+ dims: int = 3,
61
+ spatial_upsample: bool = True,
62
+ temporal_upsample: bool = False,
63
+ ):
64
+ super().__init__()
65
+
66
+ self.in_channels = in_channels
67
+ self.mid_channels = mid_channels
68
+ self.num_blocks_per_stage = num_blocks_per_stage
69
+ self.dims = dims
70
+ self.spatial_upsample = spatial_upsample
71
+ self.temporal_upsample = temporal_upsample
72
+
73
+ Conv = nn.Conv2d if dims == 2 else nn.Conv3d
74
+
75
+ self.initial_conv = Conv(in_channels, mid_channels, kernel_size=3, padding=1)
76
+ self.initial_norm = nn.GroupNorm(32, mid_channels)
77
+ self.initial_activation = nn.SiLU()
78
+
79
+ self.res_blocks = nn.ModuleList(
80
+ [ResBlock(mid_channels, dims=dims) for _ in range(num_blocks_per_stage)]
81
+ )
82
+
83
+ if spatial_upsample and temporal_upsample:
84
+ self.upsampler = nn.Sequential(
85
+ nn.Conv3d(mid_channels, 8 * mid_channels, kernel_size=3, padding=1),
86
+ PixelShuffleND(3),
87
+ )
88
+ elif spatial_upsample:
89
+ self.upsampler = nn.Sequential(
90
+ nn.Conv2d(mid_channels, 4 * mid_channels, kernel_size=3, padding=1),
91
+ PixelShuffleND(2),
92
+ )
93
+ elif temporal_upsample:
94
+ self.upsampler = nn.Sequential(
95
+ nn.Conv3d(mid_channels, 2 * mid_channels, kernel_size=3, padding=1),
96
+ PixelShuffleND(1),
97
+ )
98
+ else:
99
+ raise ValueError(
100
+ "Either spatial_upsample or temporal_upsample must be True"
101
+ )
102
+
103
+ self.post_upsample_res_blocks = nn.ModuleList(
104
+ [ResBlock(mid_channels, dims=dims) for _ in range(num_blocks_per_stage)]
105
+ )
106
+
107
+ self.final_conv = Conv(mid_channels, in_channels, kernel_size=3, padding=1)
108
+
109
+ def forward(self, latent: torch.Tensor) -> torch.Tensor:
110
+ b, c, f, h, w = latent.shape
111
+
112
+ if self.dims == 2:
113
+ x = rearrange(latent, "b c f h w -> (b f) c h w")
114
+ x = self.initial_conv(x)
115
+ x = self.initial_norm(x)
116
+ x = self.initial_activation(x)
117
+
118
+ for block in self.res_blocks:
119
+ x = block(x)
120
+
121
+ x = self.upsampler(x)
122
+
123
+ for block in self.post_upsample_res_blocks:
124
+ x = block(x)
125
+
126
+ x = self.final_conv(x)
127
+ x = rearrange(x, "(b f) c h w -> b c f h w", b=b, f=f)
128
+ else:
129
+ x = self.initial_conv(latent)
130
+ x = self.initial_norm(x)
131
+ x = self.initial_activation(x)
132
+
133
+ for block in self.res_blocks:
134
+ x = block(x)
135
+
136
+ if self.temporal_upsample:
137
+ x = self.upsampler(x)
138
+ x = x[:, :, 1:, :, :]
139
+ else:
140
+ x = rearrange(x, "b c f h w -> (b f) c h w")
141
+ x = self.upsampler(x)
142
+ x = rearrange(x, "(b f) c h w -> b c f h w", b=b, f=f)
143
+
144
+ for block in self.post_upsample_res_blocks:
145
+ x = block(x)
146
+
147
+ x = self.final_conv(x)
148
+
149
+ return x
150
+
151
+ @classmethod
152
+ def from_config(cls, config):
153
+ return cls(
154
+ in_channels=config.get("in_channels", 4),
155
+ mid_channels=config.get("mid_channels", 128),
156
+ num_blocks_per_stage=config.get("num_blocks_per_stage", 4),
157
+ dims=config.get("dims", 2),
158
+ spatial_upsample=config.get("spatial_upsample", True),
159
+ temporal_upsample=config.get("temporal_upsample", False),
160
+ )
161
+
162
+ def config(self):
163
+ return {
164
+ "_class_name": "LatentUpsampler",
165
+ "in_channels": self.in_channels,
166
+ "mid_channels": self.mid_channels,
167
+ "num_blocks_per_stage": self.num_blocks_per_stage,
168
+ "dims": self.dims,
169
+ "spatial_upsample": self.spatial_upsample,
170
+ "temporal_upsample": self.temporal_upsample,
171
+ }
172
+
173
+ @classmethod
174
+ def from_pretrained(
175
+ cls,
176
+ pretrained_model_path: Optional[Union[str, os.PathLike]],
177
+ *args,
178
+ **kwargs,
179
+ ):
180
+ pretrained_model_path = Path(pretrained_model_path)
181
+ if pretrained_model_path.is_file() and str(pretrained_model_path).endswith(
182
+ ".safetensors"
183
+ ):
184
+ state_dict = {}
185
+ with safe_open(pretrained_model_path, framework="pt", device="cpu") as f:
186
+ metadata = f.metadata()
187
+ for k in f.keys():
188
+ state_dict[k] = f.get_tensor(k)
189
+ config = json.loads(metadata["config"])
190
+ with torch.device("meta"):
191
+ latent_upsampler = LatentUpsampler.from_config(config)
192
+ latent_upsampler.load_state_dict(state_dict, assign=True)
193
+ return latent_upsampler
194
+
195
+
196
+ if __name__ == "__main__":
197
+ latent_upsampler = LatentUpsampler(num_blocks_per_stage=4, dims=3)
198
+ print(latent_upsampler)
199
+ total_params = sum(p.numel() for p in latent_upsampler.parameters())
200
+ print(f"Total number of parameters: {total_params:,}")
201
+ latent = torch.randn(1, 128, 9, 16, 16)
202
+ upsampled_latent = latent_upsampler(latent)
203
+ print(f"Upsampled latent shape: {upsampled_latent.shape}")
ltx_video/models/autoencoders/pixel_norm.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+
4
+
5
+ class PixelNorm(nn.Module):
6
+ def __init__(self, dim=1, eps=1e-8):
7
+ super(PixelNorm, self).__init__()
8
+ self.dim = dim
9
+ self.eps = eps
10
+
11
+ def forward(self, x):
12
+ return x / torch.sqrt(torch.mean(x**2, dim=self.dim, keepdim=True) + self.eps)
ltx_video/models/autoencoders/pixel_shuffle.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from einops import rearrange
3
+
4
+
5
+ class PixelShuffleND(nn.Module):
6
+ def __init__(self, dims, upscale_factors=(2, 2, 2)):
7
+ super().__init__()
8
+ assert dims in [1, 2, 3], "dims must be 1, 2, or 3"
9
+ self.dims = dims
10
+ self.upscale_factors = upscale_factors
11
+
12
+ def forward(self, x):
13
+ if self.dims == 3:
14
+ return rearrange(
15
+ x,
16
+ "b (c p1 p2 p3) d h w -> b c (d p1) (h p2) (w p3)",
17
+ p1=self.upscale_factors[0],
18
+ p2=self.upscale_factors[1],
19
+ p3=self.upscale_factors[2],
20
+ )
21
+ elif self.dims == 2:
22
+ return rearrange(
23
+ x,
24
+ "b (c p1 p2) h w -> b c (h p1) (w p2)",
25
+ p1=self.upscale_factors[0],
26
+ p2=self.upscale_factors[1],
27
+ )
28
+ elif self.dims == 1:
29
+ return rearrange(
30
+ x,
31
+ "b (c p1) f h w -> b c (f p1) h w",
32
+ p1=self.upscale_factors[0],
33
+ )
ltx_video/models/autoencoders/vae.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union
2
+
3
+ import torch
4
+ import inspect
5
+ import math
6
+ import torch.nn as nn
7
+ from diffusers import ConfigMixin, ModelMixin
8
+ from diffusers.models.autoencoders.vae import (
9
+ DecoderOutput,
10
+ DiagonalGaussianDistribution,
11
+ )
12
+ from diffusers.models.modeling_outputs import AutoencoderKLOutput
13
+ from ltx_video.models.autoencoders.conv_nd_factory import make_conv_nd
14
+
15
+
16
+ class AutoencoderKLWrapper(ModelMixin, ConfigMixin):
17
+ """Variational Autoencoder (VAE) model with KL loss.
18
+
19
+ VAE from the paper Auto-Encoding Variational Bayes by Diederik P. Kingma and Max Welling.
20
+ This model is a wrapper around an encoder and a decoder, and it adds a KL loss term to the reconstruction loss.
21
+
22
+ Args:
23
+ encoder (`nn.Module`):
24
+ Encoder module.
25
+ decoder (`nn.Module`):
26
+ Decoder module.
27
+ latent_channels (`int`, *optional*, defaults to 4):
28
+ Number of latent channels.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ encoder: nn.Module,
34
+ decoder: nn.Module,
35
+ latent_channels: int = 4,
36
+ dims: int = 2,
37
+ sample_size=512,
38
+ use_quant_conv: bool = True,
39
+ normalize_latent_channels: bool = False,
40
+ ):
41
+ super().__init__()
42
+
43
+ # pass init params to Encoder
44
+ self.encoder = encoder
45
+ self.use_quant_conv = use_quant_conv
46
+ self.normalize_latent_channels = normalize_latent_channels
47
+
48
+ # pass init params to Decoder
49
+ quant_dims = 2 if dims == 2 else 3
50
+ self.decoder = decoder
51
+ if use_quant_conv:
52
+ self.quant_conv = make_conv_nd(
53
+ quant_dims, 2 * latent_channels, 2 * latent_channels, 1
54
+ )
55
+ self.post_quant_conv = make_conv_nd(
56
+ quant_dims, latent_channels, latent_channels, 1
57
+ )
58
+ else:
59
+ self.quant_conv = nn.Identity()
60
+ self.post_quant_conv = nn.Identity()
61
+
62
+ if normalize_latent_channels:
63
+ if dims == 2:
64
+ self.latent_norm_out = nn.BatchNorm2d(latent_channels, affine=False)
65
+ else:
66
+ self.latent_norm_out = nn.BatchNorm3d(latent_channels, affine=False)
67
+ else:
68
+ self.latent_norm_out = nn.Identity()
69
+ self.use_z_tiling = False
70
+ self.use_hw_tiling = False
71
+ self.dims = dims
72
+ self.z_sample_size = 1
73
+
74
+ self.decoder_params = inspect.signature(self.decoder.forward).parameters
75
+
76
+ # only relevant if vae tiling is enabled
77
+ self.set_tiling_params(sample_size=sample_size, overlap_factor=0.25)
78
+
79
+ def set_tiling_params(self, sample_size: int = 512, overlap_factor: float = 0.25):
80
+ self.tile_sample_min_size = sample_size
81
+ num_blocks = len(self.encoder.down_blocks)
82
+ self.tile_latent_min_size = int(sample_size / (2 ** (num_blocks - 1)))
83
+ self.tile_overlap_factor = overlap_factor
84
+
85
+ def enable_z_tiling(self, z_sample_size: int = 8):
86
+ r"""
87
+ Enable tiling during VAE decoding.
88
+
89
+ When this option is enabled, the VAE will split the input tensor in tiles to compute decoding in several
90
+ steps. This is useful to save some memory and allow larger batch sizes.
91
+ """
92
+ self.use_z_tiling = z_sample_size > 1
93
+ self.z_sample_size = z_sample_size
94
+ assert (
95
+ z_sample_size % 8 == 0 or z_sample_size == 1
96
+ ), f"z_sample_size must be a multiple of 8 or 1. Got {z_sample_size}."
97
+
98
+ def disable_z_tiling(self):
99
+ r"""
100
+ Disable tiling during VAE decoding. If `use_tiling` was previously invoked, this method will go back to computing
101
+ decoding in one step.
102
+ """
103
+ self.use_z_tiling = False
104
+
105
+ def enable_hw_tiling(self):
106
+ r"""
107
+ Enable tiling during VAE decoding along the height and width dimension.
108
+ """
109
+ self.use_hw_tiling = True
110
+
111
+ def disable_hw_tiling(self):
112
+ r"""
113
+ Disable tiling during VAE decoding along the height and width dimension.
114
+ """
115
+ self.use_hw_tiling = False
116
+
117
+ def _hw_tiled_encode(self, x: torch.FloatTensor, return_dict: bool = True):
118
+ overlap_size = int(self.tile_sample_min_size * (1 - self.tile_overlap_factor))
119
+ blend_extent = int(self.tile_latent_min_size * self.tile_overlap_factor)
120
+ row_limit = self.tile_latent_min_size - blend_extent
121
+
122
+ # Split the image into 512x512 tiles and encode them separately.
123
+ rows = []
124
+ for i in range(0, x.shape[3], overlap_size):
125
+ row = []
126
+ for j in range(0, x.shape[4], overlap_size):
127
+ tile = x[
128
+ :,
129
+ :,
130
+ :,
131
+ i : i + self.tile_sample_min_size,
132
+ j : j + self.tile_sample_min_size,
133
+ ]
134
+ tile = self.encoder(tile)
135
+ tile = self.quant_conv(tile)
136
+ row.append(tile)
137
+ rows.append(row)
138
+ result_rows = []
139
+ for i, row in enumerate(rows):
140
+ result_row = []
141
+ for j, tile in enumerate(row):
142
+ # blend the above tile and the left tile
143
+ # to the current tile and add the current tile to the result row
144
+ if i > 0:
145
+ tile = self.blend_v(rows[i - 1][j], tile, blend_extent)
146
+ if j > 0:
147
+ tile = self.blend_h(row[j - 1], tile, blend_extent)
148
+ result_row.append(tile[:, :, :, :row_limit, :row_limit])
149
+ result_rows.append(torch.cat(result_row, dim=4))
150
+
151
+ moments = torch.cat(result_rows, dim=3)
152
+ return moments
153
+
154
+ def blend_z(
155
+ self, a: torch.Tensor, b: torch.Tensor, blend_extent: int
156
+ ) -> torch.Tensor:
157
+ blend_extent = min(a.shape[2], b.shape[2], blend_extent)
158
+ for z in range(blend_extent):
159
+ b[:, :, z, :, :] = a[:, :, -blend_extent + z, :, :] * (
160
+ 1 - z / blend_extent
161
+ ) + b[:, :, z, :, :] * (z / blend_extent)
162
+ return b
163
+
164
+ def blend_v(
165
+ self, a: torch.Tensor, b: torch.Tensor, blend_extent: int
166
+ ) -> torch.Tensor:
167
+ blend_extent = min(a.shape[3], b.shape[3], blend_extent)
168
+ for y in range(blend_extent):
169
+ b[:, :, :, y, :] = a[:, :, :, -blend_extent + y, :] * (
170
+ 1 - y / blend_extent
171
+ ) + b[:, :, :, y, :] * (y / blend_extent)
172
+ return b
173
+
174
+ def blend_h(
175
+ self, a: torch.Tensor, b: torch.Tensor, blend_extent: int
176
+ ) -> torch.Tensor:
177
+ blend_extent = min(a.shape[4], b.shape[4], blend_extent)
178
+ for x in range(blend_extent):
179
+ b[:, :, :, :, x] = a[:, :, :, :, -blend_extent + x] * (
180
+ 1 - x / blend_extent
181
+ ) + b[:, :, :, :, x] * (x / blend_extent)
182
+ return b
183
+
184
+ def _hw_tiled_decode(self, z: torch.FloatTensor, target_shape):
185
+ overlap_size = int(self.tile_latent_min_size * (1 - self.tile_overlap_factor))
186
+ blend_extent = int(self.tile_sample_min_size * self.tile_overlap_factor)
187
+ row_limit = self.tile_sample_min_size - blend_extent
188
+ tile_target_shape = (
189
+ *target_shape[:3],
190
+ self.tile_sample_min_size,
191
+ self.tile_sample_min_size,
192
+ )
193
+ # Split z into overlapping 64x64 tiles and decode them separately.
194
+ # The tiles have an overlap to avoid seams between tiles.
195
+ rows = []
196
+ for i in range(0, z.shape[3], overlap_size):
197
+ row = []
198
+ for j in range(0, z.shape[4], overlap_size):
199
+ tile = z[
200
+ :,
201
+ :,
202
+ :,
203
+ i : i + self.tile_latent_min_size,
204
+ j : j + self.tile_latent_min_size,
205
+ ]
206
+ tile = self.post_quant_conv(tile)
207
+ decoded = self.decoder(tile, target_shape=tile_target_shape)
208
+ row.append(decoded)
209
+ rows.append(row)
210
+ result_rows = []
211
+ for i, row in enumerate(rows):
212
+ result_row = []
213
+ for j, tile in enumerate(row):
214
+ # blend the above tile and the left tile
215
+ # to the current tile and add the current tile to the result row
216
+ if i > 0:
217
+ tile = self.blend_v(rows[i - 1][j], tile, blend_extent)
218
+ if j > 0:
219
+ tile = self.blend_h(row[j - 1], tile, blend_extent)
220
+ result_row.append(tile[:, :, :, :row_limit, :row_limit])
221
+ result_rows.append(torch.cat(result_row, dim=4))
222
+
223
+ dec = torch.cat(result_rows, dim=3)
224
+ return dec
225
+
226
+ def encode(
227
+ self, z: torch.FloatTensor, return_dict: bool = True
228
+ ) -> Union[DecoderOutput, torch.FloatTensor]:
229
+ if self.use_z_tiling and z.shape[2] > self.z_sample_size > 1:
230
+ num_splits = z.shape[2] // self.z_sample_size
231
+ sizes = [self.z_sample_size] * num_splits
232
+ sizes = (
233
+ sizes + [z.shape[2] - sum(sizes)]
234
+ if z.shape[2] - sum(sizes) > 0
235
+ else sizes
236
+ )
237
+ tiles = z.split(sizes, dim=2)
238
+ moments_tiles = [
239
+ (
240
+ self._hw_tiled_encode(z_tile, return_dict)
241
+ if self.use_hw_tiling
242
+ else self._encode(z_tile)
243
+ )
244
+ for z_tile in tiles
245
+ ]
246
+ moments = torch.cat(moments_tiles, dim=2)
247
+
248
+ else:
249
+ moments = (
250
+ self._hw_tiled_encode(z, return_dict)
251
+ if self.use_hw_tiling
252
+ else self._encode(z)
253
+ )
254
+
255
+ posterior = DiagonalGaussianDistribution(moments)
256
+ if not return_dict:
257
+ return (posterior,)
258
+
259
+ return AutoencoderKLOutput(latent_dist=posterior)
260
+
261
+ def _normalize_latent_channels(self, z: torch.FloatTensor) -> torch.FloatTensor:
262
+ if isinstance(self.latent_norm_out, nn.BatchNorm3d):
263
+ _, c, _, _, _ = z.shape
264
+ z = torch.cat(
265
+ [
266
+ self.latent_norm_out(z[:, : c // 2, :, :, :]),
267
+ z[:, c // 2 :, :, :, :],
268
+ ],
269
+ dim=1,
270
+ )
271
+ elif isinstance(self.latent_norm_out, nn.BatchNorm2d):
272
+ raise NotImplementedError("BatchNorm2d not supported")
273
+ return z
274
+
275
+ def _unnormalize_latent_channels(self, z: torch.FloatTensor) -> torch.FloatTensor:
276
+ if isinstance(self.latent_norm_out, nn.BatchNorm3d):
277
+ running_mean = self.latent_norm_out.running_mean.view(1, -1, 1, 1, 1)
278
+ running_var = self.latent_norm_out.running_var.view(1, -1, 1, 1, 1)
279
+ eps = self.latent_norm_out.eps
280
+
281
+ z = z * torch.sqrt(running_var + eps) + running_mean
282
+ elif isinstance(self.latent_norm_out, nn.BatchNorm3d):
283
+ raise NotImplementedError("BatchNorm2d not supported")
284
+ return z
285
+
286
+ def _encode(self, x: torch.FloatTensor) -> AutoencoderKLOutput:
287
+ h = self.encoder(x)
288
+ moments = self.quant_conv(h)
289
+ moments = self._normalize_latent_channels(moments)
290
+ return moments
291
+
292
+ def _decode(
293
+ self,
294
+ z: torch.FloatTensor,
295
+ target_shape=None,
296
+ timestep: Optional[torch.Tensor] = None,
297
+ ) -> Union[DecoderOutput, torch.FloatTensor]:
298
+ z = self._unnormalize_latent_channels(z)
299
+ z = self.post_quant_conv(z)
300
+ if "timestep" in self.decoder_params:
301
+ dec = self.decoder(z, target_shape=target_shape, timestep=timestep)
302
+ else:
303
+ dec = self.decoder(z, target_shape=target_shape)
304
+ return dec
305
+
306
+ def decode(
307
+ self,
308
+ z: torch.FloatTensor,
309
+ return_dict: bool = True,
310
+ target_shape=None,
311
+ timestep: Optional[torch.Tensor] = None,
312
+ ) -> Union[DecoderOutput, torch.FloatTensor]:
313
+ assert target_shape is not None, "target_shape must be provided for decoding"
314
+ if self.use_z_tiling and z.shape[2] > self.z_sample_size > 1:
315
+ reduction_factor = int(
316
+ self.encoder.patch_size_t
317
+ * 2
318
+ ** (
319
+ len(self.encoder.down_blocks)
320
+ - 1
321
+ - math.sqrt(self.encoder.patch_size)
322
+ )
323
+ )
324
+ split_size = self.z_sample_size // reduction_factor
325
+ num_splits = z.shape[2] // split_size
326
+
327
+ # copy target shape, and divide frame dimension (=2) by the context size
328
+ target_shape_split = list(target_shape)
329
+ target_shape_split[2] = target_shape[2] // num_splits
330
+
331
+ decoded_tiles = [
332
+ (
333
+ self._hw_tiled_decode(z_tile, target_shape_split)
334
+ if self.use_hw_tiling
335
+ else self._decode(z_tile, target_shape=target_shape_split)
336
+ )
337
+ for z_tile in torch.tensor_split(z, num_splits, dim=2)
338
+ ]
339
+ decoded = torch.cat(decoded_tiles, dim=2)
340
+ else:
341
+ decoded = (
342
+ self._hw_tiled_decode(z, target_shape)
343
+ if self.use_hw_tiling
344
+ else self._decode(z, target_shape=target_shape, timestep=timestep)
345
+ )
346
+
347
+ if not return_dict:
348
+ return (decoded,)
349
+
350
+ return DecoderOutput(sample=decoded)
351
+
352
+ def forward(
353
+ self,
354
+ sample: torch.FloatTensor,
355
+ sample_posterior: bool = False,
356
+ return_dict: bool = True,
357
+ generator: Optional[torch.Generator] = None,
358
+ ) -> Union[DecoderOutput, torch.FloatTensor]:
359
+ r"""
360
+ Args:
361
+ sample (`torch.FloatTensor`): Input sample.
362
+ sample_posterior (`bool`, *optional*, defaults to `False`):
363
+ Whether to sample from the posterior.
364
+ return_dict (`bool`, *optional*, defaults to `True`):
365
+ Whether to return a [`DecoderOutput`] instead of a plain tuple.
366
+ generator (`torch.Generator`, *optional*):
367
+ Generator used to sample from the posterior.
368
+ """
369
+ x = sample
370
+ posterior = self.encode(x).latent_dist
371
+ if sample_posterior:
372
+ z = posterior.sample(generator=generator)
373
+ else:
374
+ z = posterior.mode()
375
+ dec = self.decode(z, target_shape=sample.shape).sample
376
+
377
+ if not return_dict:
378
+ return (dec,)
379
+
380
+ return DecoderOutput(sample=dec)
ltx_video/models/autoencoders/vae_encode.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+ import torch
3
+ from diffusers import AutoencoderKL
4
+ from einops import rearrange
5
+ from torch import Tensor
6
+
7
+
8
+ from ltx_video.models.autoencoders.causal_video_autoencoder import (
9
+ CausalVideoAutoencoder,
10
+ )
11
+ from ltx_video.models.autoencoders.video_autoencoder import (
12
+ Downsample3D,
13
+ VideoAutoencoder,
14
+ )
15
+
16
+ try:
17
+ import torch_xla.core.xla_model as xm
18
+ except ImportError:
19
+ xm = None
20
+
21
+
22
+ def vae_encode(
23
+ media_items: Tensor,
24
+ vae: AutoencoderKL,
25
+ split_size: int = 1,
26
+ vae_per_channel_normalize=False,
27
+ ) -> Tensor:
28
+ """
29
+ Encodes media items (images or videos) into latent representations using a specified VAE model.
30
+ The function supports processing batches of images or video frames and can handle the processing
31
+ in smaller sub-batches if needed.
32
+
33
+ Args:
34
+ media_items (Tensor): A torch Tensor containing the media items to encode. The expected
35
+ shape is (batch_size, channels, height, width) for images or (batch_size, channels,
36
+ frames, height, width) for videos.
37
+ vae (AutoencoderKL): An instance of the `AutoencoderKL` class from the `diffusers` library,
38
+ pre-configured and loaded with the appropriate model weights.
39
+ split_size (int, optional): The number of sub-batches to split the input batch into for encoding.
40
+ If set to more than 1, the input media items are processed in smaller batches according to
41
+ this value. Defaults to 1, which processes all items in a single batch.
42
+
43
+ Returns:
44
+ Tensor: A torch Tensor of the encoded latent representations. The shape of the tensor is adjusted
45
+ to match the input shape, scaled by the model's configuration.
46
+
47
+ Examples:
48
+ >>> import torch
49
+ >>> from diffusers import AutoencoderKL
50
+ >>> vae = AutoencoderKL.from_pretrained('your-model-name')
51
+ >>> images = torch.rand(10, 3, 8 256, 256) # Example tensor with 10 videos of 8 frames.
52
+ >>> latents = vae_encode(images, vae)
53
+ >>> print(latents.shape) # Output shape will depend on the model's latent configuration.
54
+
55
+ Note:
56
+ In case of a video, the function encodes the media item frame-by frame.
57
+ """
58
+ is_video_shaped = media_items.dim() == 5
59
+ batch_size, channels = media_items.shape[0:2]
60
+
61
+ if channels != 3:
62
+ raise ValueError(f"Expects tensors with 3 channels, got {channels}.")
63
+
64
+ if is_video_shaped and not isinstance(
65
+ vae, (VideoAutoencoder, CausalVideoAutoencoder)
66
+ ):
67
+ media_items = rearrange(media_items, "b c n h w -> (b n) c h w")
68
+ if split_size > 1:
69
+ if len(media_items) % split_size != 0:
70
+ raise ValueError(
71
+ "Error: The batch size must be divisible by 'train.vae_bs_split"
72
+ )
73
+ encode_bs = len(media_items) // split_size
74
+ # latents = [vae.encode(image_batch).latent_dist.sample() for image_batch in media_items.split(encode_bs)]
75
+ latents = []
76
+ if media_items.device.type == "xla":
77
+ xm.mark_step()
78
+ for image_batch in media_items.split(encode_bs):
79
+ latents.append(vae.encode(image_batch).latent_dist.sample())
80
+ if media_items.device.type == "xla":
81
+ xm.mark_step()
82
+ latents = torch.cat(latents, dim=0)
83
+ else:
84
+ latents = vae.encode(media_items).latent_dist.sample()
85
+
86
+ latents = normalize_latents(latents, vae, vae_per_channel_normalize)
87
+ if is_video_shaped and not isinstance(
88
+ vae, (VideoAutoencoder, CausalVideoAutoencoder)
89
+ ):
90
+ latents = rearrange(latents, "(b n) c h w -> b c n h w", b=batch_size)
91
+ return latents
92
+
93
+
94
+ def vae_decode(
95
+ latents: Tensor,
96
+ vae: AutoencoderKL,
97
+ is_video: bool = True,
98
+ split_size: int = 1,
99
+ vae_per_channel_normalize=False,
100
+ timestep=None,
101
+ ) -> Tensor:
102
+ is_video_shaped = latents.dim() == 5
103
+ batch_size = latents.shape[0]
104
+
105
+ if is_video_shaped and not isinstance(
106
+ vae, (VideoAutoencoder, CausalVideoAutoencoder)
107
+ ):
108
+ latents = rearrange(latents, "b c n h w -> (b n) c h w")
109
+ if split_size > 1:
110
+ if len(latents) % split_size != 0:
111
+ raise ValueError(
112
+ "Error: The batch size must be divisible by 'train.vae_bs_split"
113
+ )
114
+ encode_bs = len(latents) // split_size
115
+ image_batch = [
116
+ _run_decoder(
117
+ latent_batch, vae, is_video, vae_per_channel_normalize, timestep
118
+ )
119
+ for latent_batch in latents.split(encode_bs)
120
+ ]
121
+ images = torch.cat(image_batch, dim=0)
122
+ else:
123
+ images = _run_decoder(
124
+ latents, vae, is_video, vae_per_channel_normalize, timestep
125
+ )
126
+
127
+ if is_video_shaped and not isinstance(
128
+ vae, (VideoAutoencoder, CausalVideoAutoencoder)
129
+ ):
130
+ images = rearrange(images, "(b n) c h w -> b c n h w", b=batch_size)
131
+ return images
132
+
133
+
134
+ def _run_decoder(
135
+ latents: Tensor,
136
+ vae: AutoencoderKL,
137
+ is_video: bool,
138
+ vae_per_channel_normalize=False,
139
+ timestep=None,
140
+ ) -> Tensor:
141
+ if isinstance(vae, (VideoAutoencoder, CausalVideoAutoencoder)):
142
+ *_, fl, hl, wl = latents.shape
143
+ temporal_scale, spatial_scale, _ = get_vae_size_scale_factor(vae)
144
+ latents = latents.to(vae.dtype)
145
+ vae_decode_kwargs = {}
146
+ if timestep is not None:
147
+ vae_decode_kwargs["timestep"] = timestep
148
+ image = vae.decode(
149
+ un_normalize_latents(latents, vae, vae_per_channel_normalize),
150
+ return_dict=False,
151
+ target_shape=(
152
+ 1,
153
+ 3,
154
+ fl * temporal_scale if is_video else 1,
155
+ hl * spatial_scale,
156
+ wl * spatial_scale,
157
+ ),
158
+ **vae_decode_kwargs,
159
+ )[0]
160
+ else:
161
+ image = vae.decode(
162
+ un_normalize_latents(latents, vae, vae_per_channel_normalize),
163
+ return_dict=False,
164
+ )[0]
165
+ return image
166
+
167
+
168
+ def get_vae_size_scale_factor(vae: AutoencoderKL) -> float:
169
+ if isinstance(vae, CausalVideoAutoencoder):
170
+ spatial = vae.spatial_downscale_factor
171
+ temporal = vae.temporal_downscale_factor
172
+ else:
173
+ down_blocks = len(
174
+ [
175
+ block
176
+ for block in vae.encoder.down_blocks
177
+ if isinstance(block.downsample, Downsample3D)
178
+ ]
179
+ )
180
+ spatial = vae.config.patch_size * 2**down_blocks
181
+ temporal = (
182
+ vae.config.patch_size_t * 2**down_blocks
183
+ if isinstance(vae, VideoAutoencoder)
184
+ else 1
185
+ )
186
+
187
+ return (temporal, spatial, spatial)
188
+
189
+
190
+ def latent_to_pixel_coords(
191
+ latent_coords: Tensor, vae: AutoencoderKL, causal_fix: bool = False
192
+ ) -> Tensor:
193
+ """
194
+ Converts latent coordinates to pixel coordinates by scaling them according to the VAE's
195
+ configuration.
196
+
197
+ Args:
198
+ latent_coords (Tensor): A tensor of shape [batch_size, 3, num_latents]
199
+ containing the latent corner coordinates of each token.
200
+ vae (AutoencoderKL): The VAE model
201
+ causal_fix (bool): Whether to take into account the different temporal scale
202
+ of the first frame. Default = False for backwards compatibility.
203
+ Returns:
204
+ Tensor: A tensor of pixel coordinates corresponding to the input latent coordinates.
205
+ """
206
+
207
+ scale_factors = get_vae_size_scale_factor(vae)
208
+ causal_fix = isinstance(vae, CausalVideoAutoencoder) and causal_fix
209
+ pixel_coords = latent_to_pixel_coords_from_factors(
210
+ latent_coords, scale_factors, causal_fix
211
+ )
212
+ return pixel_coords
213
+
214
+
215
+ def latent_to_pixel_coords_from_factors(
216
+ latent_coords: Tensor, scale_factors: Tuple, causal_fix: bool = False
217
+ ) -> Tensor:
218
+ pixel_coords = (
219
+ latent_coords
220
+ * torch.tensor(scale_factors, device=latent_coords.device)[None, :, None]
221
+ )
222
+ if causal_fix:
223
+ # Fix temporal scale for first frame to 1 due to causality
224
+ pixel_coords[:, 0] = (pixel_coords[:, 0] + 1 - scale_factors[0]).clamp(min=0)
225
+ return pixel_coords
226
+
227
+
228
+ def normalize_latents(
229
+ latents: Tensor, vae: AutoencoderKL, vae_per_channel_normalize: bool = False
230
+ ) -> Tensor:
231
+ return (
232
+ (latents - vae.mean_of_means.to(latents.dtype).view(1, -1, 1, 1, 1))
233
+ / vae.std_of_means.to(latents.dtype).view(1, -1, 1, 1, 1)
234
+ if vae_per_channel_normalize
235
+ else latents * vae.config.scaling_factor
236
+ )
237
+
238
+
239
+ def un_normalize_latents(
240
+ latents: Tensor, vae: AutoencoderKL, vae_per_channel_normalize: bool = False
241
+ ) -> Tensor:
242
+ return (
243
+ latents * vae.std_of_means.to(latents.dtype).view(1, -1, 1, 1, 1)
244
+ + vae.mean_of_means.to(latents.dtype).view(1, -1, 1, 1, 1)
245
+ if vae_per_channel_normalize
246
+ else latents / vae.config.scaling_factor
247
+ )
ltx_video/models/autoencoders/video_autoencoder.py ADDED
@@ -0,0 +1,1045 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from functools import partial
4
+ from types import SimpleNamespace
5
+ from typing import Any, Mapping, Optional, Tuple, Union
6
+
7
+ import torch
8
+ from einops import rearrange
9
+ from torch import nn
10
+ from torch.nn import functional
11
+
12
+ from diffusers.utils import logging
13
+
14
+ from ltx_video.utils.torch_utils import Identity
15
+ from ltx_video.models.autoencoders.conv_nd_factory import make_conv_nd, make_linear_nd
16
+ from ltx_video.models.autoencoders.pixel_norm import PixelNorm
17
+ from ltx_video.models.autoencoders.vae import AutoencoderKLWrapper
18
+
19
+ logger = logging.get_logger(__name__)
20
+
21
+
22
+ class VideoAutoencoder(AutoencoderKLWrapper):
23
+ @classmethod
24
+ def from_pretrained(
25
+ cls,
26
+ pretrained_model_name_or_path: Optional[Union[str, os.PathLike]],
27
+ *args,
28
+ **kwargs,
29
+ ):
30
+ config_local_path = pretrained_model_name_or_path / "config.json"
31
+ config = cls.load_config(config_local_path, **kwargs)
32
+ video_vae = cls.from_config(config)
33
+ video_vae.to(kwargs["torch_dtype"])
34
+
35
+ model_local_path = pretrained_model_name_or_path / "autoencoder.pth"
36
+ ckpt_state_dict = torch.load(model_local_path)
37
+ video_vae.load_state_dict(ckpt_state_dict)
38
+
39
+ statistics_local_path = (
40
+ pretrained_model_name_or_path / "per_channel_statistics.json"
41
+ )
42
+ if statistics_local_path.exists():
43
+ with open(statistics_local_path, "r") as file:
44
+ data = json.load(file)
45
+ transposed_data = list(zip(*data["data"]))
46
+ data_dict = {
47
+ col: torch.tensor(vals)
48
+ for col, vals in zip(data["columns"], transposed_data)
49
+ }
50
+ video_vae.register_buffer("std_of_means", data_dict["std-of-means"])
51
+ video_vae.register_buffer(
52
+ "mean_of_means",
53
+ data_dict.get(
54
+ "mean-of-means", torch.zeros_like(data_dict["std-of-means"])
55
+ ),
56
+ )
57
+
58
+ return video_vae
59
+
60
+ @staticmethod
61
+ def from_config(config):
62
+ assert (
63
+ config["_class_name"] == "VideoAutoencoder"
64
+ ), "config must have _class_name=VideoAutoencoder"
65
+ if isinstance(config["dims"], list):
66
+ config["dims"] = tuple(config["dims"])
67
+
68
+ assert config["dims"] in [2, 3, (2, 1)], "dims must be 2, 3 or (2, 1)"
69
+
70
+ double_z = config.get("double_z", True)
71
+ latent_log_var = config.get(
72
+ "latent_log_var", "per_channel" if double_z else "none"
73
+ )
74
+ use_quant_conv = config.get("use_quant_conv", True)
75
+
76
+ if use_quant_conv and latent_log_var == "uniform":
77
+ raise ValueError("uniform latent_log_var requires use_quant_conv=False")
78
+
79
+ encoder = Encoder(
80
+ dims=config["dims"],
81
+ in_channels=config.get("in_channels", 3),
82
+ out_channels=config["latent_channels"],
83
+ block_out_channels=config["block_out_channels"],
84
+ patch_size=config.get("patch_size", 1),
85
+ latent_log_var=latent_log_var,
86
+ norm_layer=config.get("norm_layer", "group_norm"),
87
+ patch_size_t=config.get("patch_size_t", config.get("patch_size", 1)),
88
+ add_channel_padding=config.get("add_channel_padding", False),
89
+ )
90
+
91
+ decoder = Decoder(
92
+ dims=config["dims"],
93
+ in_channels=config["latent_channels"],
94
+ out_channels=config.get("out_channels", 3),
95
+ block_out_channels=config["block_out_channels"],
96
+ patch_size=config.get("patch_size", 1),
97
+ norm_layer=config.get("norm_layer", "group_norm"),
98
+ patch_size_t=config.get("patch_size_t", config.get("patch_size", 1)),
99
+ add_channel_padding=config.get("add_channel_padding", False),
100
+ )
101
+
102
+ dims = config["dims"]
103
+ return VideoAutoencoder(
104
+ encoder=encoder,
105
+ decoder=decoder,
106
+ latent_channels=config["latent_channels"],
107
+ dims=dims,
108
+ use_quant_conv=use_quant_conv,
109
+ )
110
+
111
+ @property
112
+ def config(self):
113
+ return SimpleNamespace(
114
+ _class_name="VideoAutoencoder",
115
+ dims=self.dims,
116
+ in_channels=self.encoder.conv_in.in_channels
117
+ // (self.encoder.patch_size_t * self.encoder.patch_size**2),
118
+ out_channels=self.decoder.conv_out.out_channels
119
+ // (self.decoder.patch_size_t * self.decoder.patch_size**2),
120
+ latent_channels=self.decoder.conv_in.in_channels,
121
+ block_out_channels=[
122
+ self.encoder.down_blocks[i].res_blocks[-1].conv1.out_channels
123
+ for i in range(len(self.encoder.down_blocks))
124
+ ],
125
+ scaling_factor=1.0,
126
+ norm_layer=self.encoder.norm_layer,
127
+ patch_size=self.encoder.patch_size,
128
+ latent_log_var=self.encoder.latent_log_var,
129
+ use_quant_conv=self.use_quant_conv,
130
+ patch_size_t=self.encoder.patch_size_t,
131
+ add_channel_padding=self.encoder.add_channel_padding,
132
+ )
133
+
134
+ @property
135
+ def is_video_supported(self):
136
+ """
137
+ Check if the model supports video inputs of shape (B, C, F, H, W). Otherwise, the model only supports 2D images.
138
+ """
139
+ return self.dims != 2
140
+
141
+ @property
142
+ def downscale_factor(self):
143
+ return self.encoder.downsample_factor
144
+
145
+ def to_json_string(self) -> str:
146
+ import json
147
+
148
+ return json.dumps(self.config.__dict__)
149
+
150
+ def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True):
151
+ model_keys = set(name for name, _ in self.named_parameters())
152
+
153
+ key_mapping = {
154
+ ".resnets.": ".res_blocks.",
155
+ "downsamplers.0": "downsample",
156
+ "upsamplers.0": "upsample",
157
+ }
158
+
159
+ converted_state_dict = {}
160
+ for key, value in state_dict.items():
161
+ for k, v in key_mapping.items():
162
+ key = key.replace(k, v)
163
+
164
+ if "norm" in key and key not in model_keys:
165
+ logger.info(
166
+ f"Removing key {key} from state_dict as it is not present in the model"
167
+ )
168
+ continue
169
+
170
+ converted_state_dict[key] = value
171
+
172
+ super().load_state_dict(converted_state_dict, strict=strict)
173
+
174
+ def last_layer(self):
175
+ if hasattr(self.decoder, "conv_out"):
176
+ if isinstance(self.decoder.conv_out, nn.Sequential):
177
+ last_layer = self.decoder.conv_out[-1]
178
+ else:
179
+ last_layer = self.decoder.conv_out
180
+ else:
181
+ last_layer = self.decoder.layers[-1]
182
+ return last_layer
183
+
184
+
185
+ class Encoder(nn.Module):
186
+ r"""
187
+ The `Encoder` layer of a variational autoencoder that encodes its input into a latent representation.
188
+
189
+ Args:
190
+ in_channels (`int`, *optional*, defaults to 3):
191
+ The number of input channels.
192
+ out_channels (`int`, *optional*, defaults to 3):
193
+ The number of output channels.
194
+ block_out_channels (`Tuple[int, ...]`, *optional*, defaults to `(64,)`):
195
+ The number of output channels for each block.
196
+ layers_per_block (`int`, *optional*, defaults to 2):
197
+ The number of layers per block.
198
+ norm_num_groups (`int`, *optional*, defaults to 32):
199
+ The number of groups for normalization.
200
+ patch_size (`int`, *optional*, defaults to 1):
201
+ The patch size to use. Should be a power of 2.
202
+ norm_layer (`str`, *optional*, defaults to `group_norm`):
203
+ The normalization layer to use. Can be either `group_norm` or `pixel_norm`.
204
+ latent_log_var (`str`, *optional*, defaults to `per_channel`):
205
+ The number of channels for the log variance. Can be either `per_channel`, `uniform`, or `none`.
206
+ """
207
+
208
+ def __init__(
209
+ self,
210
+ dims: Union[int, Tuple[int, int]] = 3,
211
+ in_channels: int = 3,
212
+ out_channels: int = 3,
213
+ block_out_channels: Tuple[int, ...] = (64,),
214
+ layers_per_block: int = 2,
215
+ norm_num_groups: int = 32,
216
+ patch_size: Union[int, Tuple[int]] = 1,
217
+ norm_layer: str = "group_norm", # group_norm, pixel_norm
218
+ latent_log_var: str = "per_channel",
219
+ patch_size_t: Optional[int] = None,
220
+ add_channel_padding: Optional[bool] = False,
221
+ ):
222
+ super().__init__()
223
+ self.patch_size = patch_size
224
+ self.patch_size_t = patch_size_t if patch_size_t is not None else patch_size
225
+ self.add_channel_padding = add_channel_padding
226
+ self.layers_per_block = layers_per_block
227
+ self.norm_layer = norm_layer
228
+ self.latent_channels = out_channels
229
+ self.latent_log_var = latent_log_var
230
+ if add_channel_padding:
231
+ in_channels = in_channels * self.patch_size**3
232
+ else:
233
+ in_channels = in_channels * self.patch_size_t * self.patch_size**2
234
+ self.in_channels = in_channels
235
+ output_channel = block_out_channels[0]
236
+
237
+ self.conv_in = make_conv_nd(
238
+ dims=dims,
239
+ in_channels=in_channels,
240
+ out_channels=output_channel,
241
+ kernel_size=3,
242
+ stride=1,
243
+ padding=1,
244
+ )
245
+
246
+ self.down_blocks = nn.ModuleList([])
247
+
248
+ for i in range(len(block_out_channels)):
249
+ input_channel = output_channel
250
+ output_channel = block_out_channels[i]
251
+ is_final_block = i == len(block_out_channels) - 1
252
+
253
+ down_block = DownEncoderBlock3D(
254
+ dims=dims,
255
+ in_channels=input_channel,
256
+ out_channels=output_channel,
257
+ num_layers=self.layers_per_block,
258
+ add_downsample=not is_final_block and 2**i >= patch_size,
259
+ resnet_eps=1e-6,
260
+ downsample_padding=0,
261
+ resnet_groups=norm_num_groups,
262
+ norm_layer=norm_layer,
263
+ )
264
+ self.down_blocks.append(down_block)
265
+
266
+ self.mid_block = UNetMidBlock3D(
267
+ dims=dims,
268
+ in_channels=block_out_channels[-1],
269
+ num_layers=self.layers_per_block,
270
+ resnet_eps=1e-6,
271
+ resnet_groups=norm_num_groups,
272
+ norm_layer=norm_layer,
273
+ )
274
+
275
+ # out
276
+ if norm_layer == "group_norm":
277
+ self.conv_norm_out = nn.GroupNorm(
278
+ num_channels=block_out_channels[-1],
279
+ num_groups=norm_num_groups,
280
+ eps=1e-6,
281
+ )
282
+ elif norm_layer == "pixel_norm":
283
+ self.conv_norm_out = PixelNorm()
284
+ self.conv_act = nn.SiLU()
285
+
286
+ conv_out_channels = out_channels
287
+ if latent_log_var == "per_channel":
288
+ conv_out_channels *= 2
289
+ elif latent_log_var == "uniform":
290
+ conv_out_channels += 1
291
+ elif latent_log_var != "none":
292
+ raise ValueError(f"Invalid latent_log_var: {latent_log_var}")
293
+ self.conv_out = make_conv_nd(
294
+ dims, block_out_channels[-1], conv_out_channels, 3, padding=1
295
+ )
296
+
297
+ self.gradient_checkpointing = False
298
+
299
+ @property
300
+ def downscale_factor(self):
301
+ return (
302
+ 2
303
+ ** len(
304
+ [
305
+ block
306
+ for block in self.down_blocks
307
+ if isinstance(block.downsample, Downsample3D)
308
+ ]
309
+ )
310
+ * self.patch_size
311
+ )
312
+
313
+ def forward(
314
+ self, sample: torch.FloatTensor, return_features=False
315
+ ) -> torch.FloatTensor:
316
+ r"""The forward method of the `Encoder` class."""
317
+
318
+ downsample_in_time = sample.shape[2] != 1
319
+
320
+ # patchify
321
+ patch_size_t = self.patch_size_t if downsample_in_time else 1
322
+ sample = patchify(
323
+ sample,
324
+ patch_size_hw=self.patch_size,
325
+ patch_size_t=patch_size_t,
326
+ add_channel_padding=self.add_channel_padding,
327
+ )
328
+
329
+ sample = self.conv_in(sample)
330
+
331
+ checkpoint_fn = (
332
+ partial(torch.utils.checkpoint.checkpoint, use_reentrant=False)
333
+ if self.gradient_checkpointing and self.training
334
+ else lambda x: x
335
+ )
336
+
337
+ if return_features:
338
+ features = []
339
+ for down_block in self.down_blocks:
340
+ sample = checkpoint_fn(down_block)(
341
+ sample, downsample_in_time=downsample_in_time
342
+ )
343
+ if return_features:
344
+ features.append(sample)
345
+
346
+ sample = checkpoint_fn(self.mid_block)(sample)
347
+
348
+ # post-process
349
+ sample = self.conv_norm_out(sample)
350
+ sample = self.conv_act(sample)
351
+ sample = self.conv_out(sample)
352
+
353
+ if self.latent_log_var == "uniform":
354
+ last_channel = sample[:, -1:, ...]
355
+ num_dims = sample.dim()
356
+
357
+ if num_dims == 4:
358
+ # For shape (B, C, H, W)
359
+ repeated_last_channel = last_channel.repeat(
360
+ 1, sample.shape[1] - 2, 1, 1
361
+ )
362
+ sample = torch.cat([sample, repeated_last_channel], dim=1)
363
+ elif num_dims == 5:
364
+ # For shape (B, C, F, H, W)
365
+ repeated_last_channel = last_channel.repeat(
366
+ 1, sample.shape[1] - 2, 1, 1, 1
367
+ )
368
+ sample = torch.cat([sample, repeated_last_channel], dim=1)
369
+ else:
370
+ raise ValueError(f"Invalid input shape: {sample.shape}")
371
+
372
+ if return_features:
373
+ features.append(sample[:, : self.latent_channels, ...])
374
+ return sample, features
375
+ return sample
376
+
377
+
378
+ class Decoder(nn.Module):
379
+ r"""
380
+ The `Decoder` layer of a variational autoencoder that decodes its latent representation into an output sample.
381
+
382
+ Args:
383
+ in_channels (`int`, *optional*, defaults to 3):
384
+ The number of input channels.
385
+ out_channels (`int`, *optional*, defaults to 3):
386
+ The number of output channels.
387
+ block_out_channels (`Tuple[int, ...]`, *optional*, defaults to `(64,)`):
388
+ The number of output channels for each block.
389
+ layers_per_block (`int`, *optional*, defaults to 2):
390
+ The number of layers per block.
391
+ norm_num_groups (`int`, *optional*, defaults to 32):
392
+ The number of groups for normalization.
393
+ patch_size (`int`, *optional*, defaults to 1):
394
+ The patch size to use. Should be a power of 2.
395
+ norm_layer (`str`, *optional*, defaults to `group_norm`):
396
+ The normalization layer to use. Can be either `group_norm` or `pixel_norm`.
397
+ """
398
+
399
+ def __init__(
400
+ self,
401
+ dims,
402
+ in_channels: int = 3,
403
+ out_channels: int = 3,
404
+ block_out_channels: Tuple[int, ...] = (64,),
405
+ layers_per_block: int = 2,
406
+ norm_num_groups: int = 32,
407
+ patch_size: int = 1,
408
+ norm_layer: str = "group_norm",
409
+ patch_size_t: Optional[int] = None,
410
+ add_channel_padding: Optional[bool] = False,
411
+ ):
412
+ super().__init__()
413
+ self.patch_size = patch_size
414
+ self.patch_size_t = patch_size_t if patch_size_t is not None else patch_size
415
+ self.add_channel_padding = add_channel_padding
416
+ self.layers_per_block = layers_per_block
417
+ if add_channel_padding:
418
+ out_channels = out_channels * self.patch_size**3
419
+ else:
420
+ out_channels = out_channels * self.patch_size_t * self.patch_size**2
421
+ self.out_channels = out_channels
422
+
423
+ self.conv_in = make_conv_nd(
424
+ dims,
425
+ in_channels,
426
+ block_out_channels[-1],
427
+ kernel_size=3,
428
+ stride=1,
429
+ padding=1,
430
+ )
431
+
432
+ self.mid_block = None
433
+ self.up_blocks = nn.ModuleList([])
434
+
435
+ self.mid_block = UNetMidBlock3D(
436
+ dims=dims,
437
+ in_channels=block_out_channels[-1],
438
+ num_layers=self.layers_per_block,
439
+ resnet_eps=1e-6,
440
+ resnet_groups=norm_num_groups,
441
+ norm_layer=norm_layer,
442
+ )
443
+
444
+ reversed_block_out_channels = list(reversed(block_out_channels))
445
+ output_channel = reversed_block_out_channels[0]
446
+ for i in range(len(reversed_block_out_channels)):
447
+ prev_output_channel = output_channel
448
+ output_channel = reversed_block_out_channels[i]
449
+
450
+ is_final_block = i == len(block_out_channels) - 1
451
+
452
+ up_block = UpDecoderBlock3D(
453
+ dims=dims,
454
+ num_layers=self.layers_per_block + 1,
455
+ in_channels=prev_output_channel,
456
+ out_channels=output_channel,
457
+ add_upsample=not is_final_block
458
+ and 2 ** (len(block_out_channels) - i - 1) > patch_size,
459
+ resnet_eps=1e-6,
460
+ resnet_groups=norm_num_groups,
461
+ norm_layer=norm_layer,
462
+ )
463
+ self.up_blocks.append(up_block)
464
+
465
+ if norm_layer == "group_norm":
466
+ self.conv_norm_out = nn.GroupNorm(
467
+ num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=1e-6
468
+ )
469
+ elif norm_layer == "pixel_norm":
470
+ self.conv_norm_out = PixelNorm()
471
+
472
+ self.conv_act = nn.SiLU()
473
+ self.conv_out = make_conv_nd(
474
+ dims, block_out_channels[0], out_channels, 3, padding=1
475
+ )
476
+
477
+ self.gradient_checkpointing = False
478
+
479
+ def forward(self, sample: torch.FloatTensor, target_shape) -> torch.FloatTensor:
480
+ r"""The forward method of the `Decoder` class."""
481
+ assert target_shape is not None, "target_shape must be provided"
482
+ upsample_in_time = sample.shape[2] < target_shape[2]
483
+
484
+ sample = self.conv_in(sample)
485
+
486
+ upscale_dtype = next(iter(self.up_blocks.parameters())).dtype
487
+
488
+ checkpoint_fn = (
489
+ partial(torch.utils.checkpoint.checkpoint, use_reentrant=False)
490
+ if self.gradient_checkpointing and self.training
491
+ else lambda x: x
492
+ )
493
+
494
+ sample = checkpoint_fn(self.mid_block)(sample)
495
+ sample = sample.to(upscale_dtype)
496
+
497
+ for up_block in self.up_blocks:
498
+ sample = checkpoint_fn(up_block)(sample, upsample_in_time=upsample_in_time)
499
+
500
+ # post-process
501
+ sample = self.conv_norm_out(sample)
502
+ sample = self.conv_act(sample)
503
+ sample = self.conv_out(sample)
504
+
505
+ # un-patchify
506
+ patch_size_t = self.patch_size_t if upsample_in_time else 1
507
+ sample = unpatchify(
508
+ sample,
509
+ patch_size_hw=self.patch_size,
510
+ patch_size_t=patch_size_t,
511
+ add_channel_padding=self.add_channel_padding,
512
+ )
513
+
514
+ return sample
515
+
516
+
517
+ class DownEncoderBlock3D(nn.Module):
518
+ def __init__(
519
+ self,
520
+ dims: Union[int, Tuple[int, int]],
521
+ in_channels: int,
522
+ out_channels: int,
523
+ dropout: float = 0.0,
524
+ num_layers: int = 1,
525
+ resnet_eps: float = 1e-6,
526
+ resnet_groups: int = 32,
527
+ add_downsample: bool = True,
528
+ downsample_padding: int = 1,
529
+ norm_layer: str = "group_norm",
530
+ ):
531
+ super().__init__()
532
+ res_blocks = []
533
+
534
+ for i in range(num_layers):
535
+ in_channels = in_channels if i == 0 else out_channels
536
+ res_blocks.append(
537
+ ResnetBlock3D(
538
+ dims=dims,
539
+ in_channels=in_channels,
540
+ out_channels=out_channels,
541
+ eps=resnet_eps,
542
+ groups=resnet_groups,
543
+ dropout=dropout,
544
+ norm_layer=norm_layer,
545
+ )
546
+ )
547
+
548
+ self.res_blocks = nn.ModuleList(res_blocks)
549
+
550
+ if add_downsample:
551
+ self.downsample = Downsample3D(
552
+ dims,
553
+ out_channels,
554
+ out_channels=out_channels,
555
+ padding=downsample_padding,
556
+ )
557
+ else:
558
+ self.downsample = Identity()
559
+
560
+ def forward(
561
+ self, hidden_states: torch.FloatTensor, downsample_in_time
562
+ ) -> torch.FloatTensor:
563
+ for resnet in self.res_blocks:
564
+ hidden_states = resnet(hidden_states)
565
+
566
+ hidden_states = self.downsample(
567
+ hidden_states, downsample_in_time=downsample_in_time
568
+ )
569
+
570
+ return hidden_states
571
+
572
+
573
+ class UNetMidBlock3D(nn.Module):
574
+ """
575
+ A 3D UNet mid-block [`UNetMidBlock3D`] with multiple residual blocks.
576
+
577
+ Args:
578
+ in_channels (`int`): The number of input channels.
579
+ dropout (`float`, *optional*, defaults to 0.0): The dropout rate.
580
+ num_layers (`int`, *optional*, defaults to 1): The number of residual blocks.
581
+ resnet_eps (`float`, *optional*, 1e-6 ): The epsilon value for the resnet blocks.
582
+ resnet_groups (`int`, *optional*, defaults to 32):
583
+ The number of groups to use in the group normalization layers of the resnet blocks.
584
+
585
+ Returns:
586
+ `torch.FloatTensor`: The output of the last residual block, which is a tensor of shape `(batch_size,
587
+ in_channels, height, width)`.
588
+
589
+ """
590
+
591
+ def __init__(
592
+ self,
593
+ dims: Union[int, Tuple[int, int]],
594
+ in_channels: int,
595
+ dropout: float = 0.0,
596
+ num_layers: int = 1,
597
+ resnet_eps: float = 1e-6,
598
+ resnet_groups: int = 32,
599
+ norm_layer: str = "group_norm",
600
+ ):
601
+ super().__init__()
602
+ resnet_groups = (
603
+ resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
604
+ )
605
+
606
+ self.res_blocks = nn.ModuleList(
607
+ [
608
+ ResnetBlock3D(
609
+ dims=dims,
610
+ in_channels=in_channels,
611
+ out_channels=in_channels,
612
+ eps=resnet_eps,
613
+ groups=resnet_groups,
614
+ dropout=dropout,
615
+ norm_layer=norm_layer,
616
+ )
617
+ for _ in range(num_layers)
618
+ ]
619
+ )
620
+
621
+ def forward(self, hidden_states: torch.FloatTensor) -> torch.FloatTensor:
622
+ for resnet in self.res_blocks:
623
+ hidden_states = resnet(hidden_states)
624
+
625
+ return hidden_states
626
+
627
+
628
+ class UpDecoderBlock3D(nn.Module):
629
+ def __init__(
630
+ self,
631
+ dims: Union[int, Tuple[int, int]],
632
+ in_channels: int,
633
+ out_channels: int,
634
+ resolution_idx: Optional[int] = None,
635
+ dropout: float = 0.0,
636
+ num_layers: int = 1,
637
+ resnet_eps: float = 1e-6,
638
+ resnet_groups: int = 32,
639
+ add_upsample: bool = True,
640
+ norm_layer: str = "group_norm",
641
+ ):
642
+ super().__init__()
643
+ res_blocks = []
644
+
645
+ for i in range(num_layers):
646
+ input_channels = in_channels if i == 0 else out_channels
647
+
648
+ res_blocks.append(
649
+ ResnetBlock3D(
650
+ dims=dims,
651
+ in_channels=input_channels,
652
+ out_channels=out_channels,
653
+ eps=resnet_eps,
654
+ groups=resnet_groups,
655
+ dropout=dropout,
656
+ norm_layer=norm_layer,
657
+ )
658
+ )
659
+
660
+ self.res_blocks = nn.ModuleList(res_blocks)
661
+
662
+ if add_upsample:
663
+ self.upsample = Upsample3D(
664
+ dims=dims, channels=out_channels, out_channels=out_channels
665
+ )
666
+ else:
667
+ self.upsample = Identity()
668
+
669
+ self.resolution_idx = resolution_idx
670
+
671
+ def forward(
672
+ self, hidden_states: torch.FloatTensor, upsample_in_time=True
673
+ ) -> torch.FloatTensor:
674
+ for resnet in self.res_blocks:
675
+ hidden_states = resnet(hidden_states)
676
+
677
+ hidden_states = self.upsample(hidden_states, upsample_in_time=upsample_in_time)
678
+
679
+ return hidden_states
680
+
681
+
682
+ class ResnetBlock3D(nn.Module):
683
+ r"""
684
+ A Resnet block.
685
+
686
+ Parameters:
687
+ in_channels (`int`): The number of channels in the input.
688
+ out_channels (`int`, *optional*, default to be `None`):
689
+ The number of output channels for the first conv layer. If None, same as `in_channels`.
690
+ dropout (`float`, *optional*, defaults to `0.0`): The dropout probability to use.
691
+ groups (`int`, *optional*, default to `32`): The number of groups to use for the first normalization layer.
692
+ eps (`float`, *optional*, defaults to `1e-6`): The epsilon to use for the normalization.
693
+ """
694
+
695
+ def __init__(
696
+ self,
697
+ dims: Union[int, Tuple[int, int]],
698
+ in_channels: int,
699
+ out_channels: Optional[int] = None,
700
+ conv_shortcut: bool = False,
701
+ dropout: float = 0.0,
702
+ groups: int = 32,
703
+ eps: float = 1e-6,
704
+ norm_layer: str = "group_norm",
705
+ ):
706
+ super().__init__()
707
+ self.in_channels = in_channels
708
+ out_channels = in_channels if out_channels is None else out_channels
709
+ self.out_channels = out_channels
710
+ self.use_conv_shortcut = conv_shortcut
711
+
712
+ if norm_layer == "group_norm":
713
+ self.norm1 = torch.nn.GroupNorm(
714
+ num_groups=groups, num_channels=in_channels, eps=eps, affine=True
715
+ )
716
+ elif norm_layer == "pixel_norm":
717
+ self.norm1 = PixelNorm()
718
+
719
+ self.non_linearity = nn.SiLU()
720
+
721
+ self.conv1 = make_conv_nd(
722
+ dims, in_channels, out_channels, kernel_size=3, stride=1, padding=1
723
+ )
724
+
725
+ if norm_layer == "group_norm":
726
+ self.norm2 = torch.nn.GroupNorm(
727
+ num_groups=groups, num_channels=out_channels, eps=eps, affine=True
728
+ )
729
+ elif norm_layer == "pixel_norm":
730
+ self.norm2 = PixelNorm()
731
+
732
+ self.dropout = torch.nn.Dropout(dropout)
733
+
734
+ self.conv2 = make_conv_nd(
735
+ dims, out_channels, out_channels, kernel_size=3, stride=1, padding=1
736
+ )
737
+
738
+ self.conv_shortcut = (
739
+ make_linear_nd(
740
+ dims=dims, in_channels=in_channels, out_channels=out_channels
741
+ )
742
+ if in_channels != out_channels
743
+ else nn.Identity()
744
+ )
745
+
746
+ def forward(
747
+ self,
748
+ input_tensor: torch.FloatTensor,
749
+ ) -> torch.FloatTensor:
750
+ hidden_states = input_tensor
751
+
752
+ hidden_states = self.norm1(hidden_states)
753
+
754
+ hidden_states = self.non_linearity(hidden_states)
755
+
756
+ hidden_states = self.conv1(hidden_states)
757
+
758
+ hidden_states = self.norm2(hidden_states)
759
+
760
+ hidden_states = self.non_linearity(hidden_states)
761
+
762
+ hidden_states = self.dropout(hidden_states)
763
+
764
+ hidden_states = self.conv2(hidden_states)
765
+
766
+ input_tensor = self.conv_shortcut(input_tensor)
767
+
768
+ output_tensor = input_tensor + hidden_states
769
+
770
+ return output_tensor
771
+
772
+
773
+ class Downsample3D(nn.Module):
774
+ def __init__(
775
+ self,
776
+ dims,
777
+ in_channels: int,
778
+ out_channels: int,
779
+ kernel_size: int = 3,
780
+ padding: int = 1,
781
+ ):
782
+ super().__init__()
783
+ stride: int = 2
784
+ self.padding = padding
785
+ self.in_channels = in_channels
786
+ self.dims = dims
787
+ self.conv = make_conv_nd(
788
+ dims=dims,
789
+ in_channels=in_channels,
790
+ out_channels=out_channels,
791
+ kernel_size=kernel_size,
792
+ stride=stride,
793
+ padding=padding,
794
+ )
795
+
796
+ def forward(self, x, downsample_in_time=True):
797
+ conv = self.conv
798
+ if self.padding == 0:
799
+ if self.dims == 2:
800
+ padding = (0, 1, 0, 1)
801
+ else:
802
+ padding = (0, 1, 0, 1, 0, 1 if downsample_in_time else 0)
803
+
804
+ x = functional.pad(x, padding, mode="constant", value=0)
805
+
806
+ if self.dims == (2, 1) and not downsample_in_time:
807
+ return conv(x, skip_time_conv=True)
808
+
809
+ return conv(x)
810
+
811
+
812
+ class Upsample3D(nn.Module):
813
+ """
814
+ An upsampling layer for 3D tensors of shape (B, C, D, H, W).
815
+
816
+ :param channels: channels in the inputs and outputs.
817
+ """
818
+
819
+ def __init__(self, dims, channels, out_channels=None):
820
+ super().__init__()
821
+ self.dims = dims
822
+ self.channels = channels
823
+ self.out_channels = out_channels or channels
824
+ self.conv = make_conv_nd(
825
+ dims, channels, out_channels, kernel_size=3, padding=1, bias=True
826
+ )
827
+
828
+ def forward(self, x, upsample_in_time):
829
+ if self.dims == 2:
830
+ x = functional.interpolate(
831
+ x, (x.shape[2] * 2, x.shape[3] * 2), mode="nearest"
832
+ )
833
+ else:
834
+ time_scale_factor = 2 if upsample_in_time else 1
835
+ # print("before:", x.shape)
836
+ b, c, d, h, w = x.shape
837
+ x = rearrange(x, "b c d h w -> (b d) c h w")
838
+ # height and width interpolate
839
+ x = functional.interpolate(
840
+ x, (x.shape[2] * 2, x.shape[3] * 2), mode="nearest"
841
+ )
842
+ _, _, h, w = x.shape
843
+
844
+ if not upsample_in_time and self.dims == (2, 1):
845
+ x = rearrange(x, "(b d) c h w -> b c d h w ", b=b, h=h, w=w)
846
+ return self.conv(x, skip_time_conv=True)
847
+
848
+ # Second ** upsampling ** which is essentially treated as a 1D convolution across the 'd' dimension
849
+ x = rearrange(x, "(b d) c h w -> (b h w) c 1 d", b=b)
850
+
851
+ # (b h w) c 1 d
852
+ new_d = x.shape[-1] * time_scale_factor
853
+ x = functional.interpolate(x, (1, new_d), mode="nearest")
854
+ # (b h w) c 1 new_d
855
+ x = rearrange(
856
+ x, "(b h w) c 1 new_d -> b c new_d h w", b=b, h=h, w=w, new_d=new_d
857
+ )
858
+ # b c d h w
859
+
860
+ # x = functional.interpolate(
861
+ # x, (x.shape[2] * time_scale_factor, x.shape[3] * 2, x.shape[4] * 2), mode="nearest"
862
+ # )
863
+ # print("after:", x.shape)
864
+
865
+ return self.conv(x)
866
+
867
+
868
+ def patchify(x, patch_size_hw, patch_size_t=1, add_channel_padding=False):
869
+ if patch_size_hw == 1 and patch_size_t == 1:
870
+ return x
871
+ if x.dim() == 4:
872
+ x = rearrange(
873
+ x, "b c (h q) (w r) -> b (c r q) h w", q=patch_size_hw, r=patch_size_hw
874
+ )
875
+ elif x.dim() == 5:
876
+ x = rearrange(
877
+ x,
878
+ "b c (f p) (h q) (w r) -> b (c p r q) f h w",
879
+ p=patch_size_t,
880
+ q=patch_size_hw,
881
+ r=patch_size_hw,
882
+ )
883
+ else:
884
+ raise ValueError(f"Invalid input shape: {x.shape}")
885
+
886
+ if (
887
+ (x.dim() == 5)
888
+ and (patch_size_hw > patch_size_t)
889
+ and (patch_size_t > 1 or add_channel_padding)
890
+ ):
891
+ channels_to_pad = x.shape[1] * (patch_size_hw // patch_size_t) - x.shape[1]
892
+ padding_zeros = torch.zeros(
893
+ x.shape[0],
894
+ channels_to_pad,
895
+ x.shape[2],
896
+ x.shape[3],
897
+ x.shape[4],
898
+ device=x.device,
899
+ dtype=x.dtype,
900
+ )
901
+ x = torch.cat([padding_zeros, x], dim=1)
902
+
903
+ return x
904
+
905
+
906
+ def unpatchify(x, patch_size_hw, patch_size_t=1, add_channel_padding=False):
907
+ if patch_size_hw == 1 and patch_size_t == 1:
908
+ return x
909
+
910
+ if (
911
+ (x.dim() == 5)
912
+ and (patch_size_hw > patch_size_t)
913
+ and (patch_size_t > 1 or add_channel_padding)
914
+ ):
915
+ channels_to_keep = int(x.shape[1] * (patch_size_t / patch_size_hw))
916
+ x = x[:, :channels_to_keep, :, :, :]
917
+
918
+ if x.dim() == 4:
919
+ x = rearrange(
920
+ x, "b (c r q) h w -> b c (h q) (w r)", q=patch_size_hw, r=patch_size_hw
921
+ )
922
+ elif x.dim() == 5:
923
+ x = rearrange(
924
+ x,
925
+ "b (c p r q) f h w -> b c (f p) (h q) (w r)",
926
+ p=patch_size_t,
927
+ q=patch_size_hw,
928
+ r=patch_size_hw,
929
+ )
930
+
931
+ return x
932
+
933
+
934
+ def create_video_autoencoder_config(
935
+ latent_channels: int = 4,
936
+ ):
937
+ config = {
938
+ "_class_name": "VideoAutoencoder",
939
+ "dims": (
940
+ 2,
941
+ 1,
942
+ ), # 2 for Conv2, 3 for Conv3d, (2, 1) for Conv2d followed by Conv1d
943
+ "in_channels": 3, # Number of input color channels (e.g., RGB)
944
+ "out_channels": 3, # Number of output color channels
945
+ "latent_channels": latent_channels, # Number of channels in the latent space representation
946
+ "block_out_channels": [
947
+ 128,
948
+ 256,
949
+ 512,
950
+ 512,
951
+ ], # Number of output channels of each encoder / decoder inner block
952
+ "patch_size": 1,
953
+ }
954
+
955
+ return config
956
+
957
+
958
+ def create_video_autoencoder_pathify4x4x4_config(
959
+ latent_channels: int = 4,
960
+ ):
961
+ config = {
962
+ "_class_name": "VideoAutoencoder",
963
+ "dims": (
964
+ 2,
965
+ 1,
966
+ ), # 2 for Conv2, 3 for Conv3d, (2, 1) for Conv2d followed by Conv1d
967
+ "in_channels": 3, # Number of input color channels (e.g., RGB)
968
+ "out_channels": 3, # Number of output color channels
969
+ "latent_channels": latent_channels, # Number of channels in the latent space representation
970
+ "block_out_channels": [512]
971
+ * 4, # Number of output channels of each encoder / decoder inner block
972
+ "patch_size": 4,
973
+ "latent_log_var": "uniform",
974
+ }
975
+
976
+ return config
977
+
978
+
979
+ def create_video_autoencoder_pathify4x4_config(
980
+ latent_channels: int = 4,
981
+ ):
982
+ config = {
983
+ "_class_name": "VideoAutoencoder",
984
+ "dims": 2, # 2 for Conv2, 3 for Conv3d, (2, 1) for Conv2d followed by Conv1d
985
+ "in_channels": 3, # Number of input color channels (e.g., RGB)
986
+ "out_channels": 3, # Number of output color channels
987
+ "latent_channels": latent_channels, # Number of channels in the latent space representation
988
+ "block_out_channels": [512]
989
+ * 4, # Number of output channels of each encoder / decoder inner block
990
+ "patch_size": 4,
991
+ "norm_layer": "pixel_norm",
992
+ }
993
+
994
+ return config
995
+
996
+
997
+ def test_vae_patchify_unpatchify():
998
+ import torch
999
+
1000
+ x = torch.randn(2, 3, 8, 64, 64)
1001
+ x_patched = patchify(x, patch_size_hw=4, patch_size_t=4)
1002
+ x_unpatched = unpatchify(x_patched, patch_size_hw=4, patch_size_t=4)
1003
+ assert torch.allclose(x, x_unpatched)
1004
+
1005
+
1006
+ def demo_video_autoencoder_forward_backward():
1007
+ # Configuration for the VideoAutoencoder
1008
+ config = create_video_autoencoder_pathify4x4x4_config()
1009
+
1010
+ # Instantiate the VideoAutoencoder with the specified configuration
1011
+ video_autoencoder = VideoAutoencoder.from_config(config)
1012
+
1013
+ print(video_autoencoder)
1014
+
1015
+ # Print the total number of parameters in the video autoencoder
1016
+ total_params = sum(p.numel() for p in video_autoencoder.parameters())
1017
+ print(f"Total number of parameters in VideoAutoencoder: {total_params:,}")
1018
+
1019
+ # Create a mock input tensor simulating a batch of videos
1020
+ # Shape: (batch_size, channels, depth, height, width)
1021
+ # E.g., 4 videos, each with 3 color channels, 16 frames, and 64x64 pixels per frame
1022
+ input_videos = torch.randn(2, 3, 8, 64, 64)
1023
+
1024
+ # Forward pass: encode and decode the input videos
1025
+ latent = video_autoencoder.encode(input_videos).latent_dist.mode()
1026
+ print(f"input shape={input_videos.shape}")
1027
+ print(f"latent shape={latent.shape}")
1028
+ reconstructed_videos = video_autoencoder.decode(
1029
+ latent, target_shape=input_videos.shape
1030
+ ).sample
1031
+
1032
+ print(f"reconstructed shape={reconstructed_videos.shape}")
1033
+
1034
+ # Calculate the loss (e.g., mean squared error)
1035
+ loss = torch.nn.functional.mse_loss(input_videos, reconstructed_videos)
1036
+
1037
+ # Perform backward pass
1038
+ loss.backward()
1039
+
1040
+ print(f"Demo completed with loss: {loss.item()}")
1041
+
1042
+
1043
+ # Ensure to call the demo function to execute the forward and backward pass
1044
+ if __name__ == "__main__":
1045
+ demo_video_autoencoder_forward_backward()