Spaces:
Running on Zero
Running on Zero
Download diffusion/training/module/pretrained_vae_base.py from hugging-apps/lidarverse: direct link, hf CLI and curl.
- Browser
- Download file 35.7 kB
-
https://huggingface.co/spaces/hugging-apps/lidarverse/resolve/main/diffusion/training/module/pretrained_vae_base.py
- Command line
-
hf download hf://spaces/hugging-apps/lidarverse/diffusion/training/module/pretrained_vae_base.py
-
curl -L -o pretrained_vae_base.py https://huggingface.co/spaces/hugging-apps/lidarverse/resolve/main/diffusion/training/module/pretrained_vae_base.py
35.7 kB
| # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import os | |
| from abc import ABC, abstractmethod | |
| import torch | |
| import torch.nn.functional as F | |
| from utils.distributed import rank0_first | |
| from utils.misc import load_from_s3_with_cache | |
| class BaseVAE(torch.nn.Module, ABC): | |
| """ | |
| Abstract base class for a Variational Autoencoder (VAE). | |
| All subclasses should implement the methods to define the behavior for encoding | |
| and decoding, along with specifying the latent channel size. | |
| """ | |
| def __init__(self, channel: int = 3, name: str = "vae"): | |
| super().__init__() | |
| self.channel = channel | |
| self.name = name | |
| def latent_ch(self) -> int: | |
| """ | |
| Returns the number of latent channels in the VAE. | |
| """ | |
| return self.channel | |
| def encode(self, state: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Encodes the input tensor into a latent representation. | |
| Args: | |
| - state (torch.Tensor): The input tensor to encode. | |
| Returns: | |
| - torch.Tensor: The encoded latent tensor. | |
| """ | |
| pass | |
| def decode(self, latent: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Decodes the latent representation back to the original space. | |
| Args: | |
| - latent (torch.Tensor): The latent tensor to decode. | |
| Returns: | |
| - torch.Tensor: The decoded tensor. | |
| """ | |
| pass | |
| def spatial_compression_factor(self) -> int: | |
| """ | |
| Returns the spatial reduction factor for the VAE. | |
| """ | |
| raise NotImplementedError("The spatial_compression_factor property must be implemented in the derived class.") | |
| class BasePretrainedImageVAE(BaseVAE): | |
| """ | |
| A base class for pretrained Variational Autoencoder (VAE) that loads mean and standard deviation values | |
| from a remote store, handles data type conversions, and normalization | |
| using provided mean and standard deviation values for latent space representation. | |
| Derived classes should load pre-trained encoder and decoder components from a remote store | |
| Attributes: | |
| latent_mean (Tensor): The mean used for normalizing the latent representation. | |
| latent_std (Tensor): The standard deviation used for normalizing the latent representation. | |
| dtype (dtype): Data type for model tensors, determined by whether bf16 is enabled. | |
| Args: | |
| mean_std_fp (str): File path to the pickle file containing mean and std of the latent space. | |
| latent_ch (int, optional): Number of latent channels (default is 16). | |
| is_image (bool, optional): Flag to indicate whether the output is an image (default is True). | |
| is_bf16 (bool, optional): Flag to use Brain Floating Point 16-bit data type (default is True). | |
| """ | |
| def __init__( | |
| self, | |
| name: str, | |
| mean_std_fp: str, | |
| latent_ch: int = 16, | |
| is_image: bool = True, | |
| is_bf16: bool = True, | |
| ) -> None: | |
| super().__init__(latent_ch, name) | |
| dtype = torch.bfloat16 if is_bf16 else torch.float32 | |
| self.dtype = dtype | |
| self.is_image = is_image | |
| self.mean_std_fp = mean_std_fp | |
| self.name = name | |
| self.backend_args = None | |
| self.register_mean_std(mean_std_fp) | |
| def register_mean_std(self, mean_std_fp: str) -> None: | |
| latent_mean, latent_std = torch.load(mean_std_fp, map_location="cuda", weights_only=True) | |
| target_shape = [1, self.latent_ch, 1, 1] if self.is_image else [1, self.latent_ch, 1, 1, 1] | |
| self.register_buffer( | |
| "latent_mean", | |
| latent_mean.to(self.dtype).reshape(*target_shape), | |
| persistent=False, | |
| ) | |
| self.register_buffer( | |
| "latent_std", | |
| latent_std.to(self.dtype).reshape(*target_shape), | |
| persistent=False, | |
| ) | |
| def encode(self, state: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Encode the input state to latent space; also handle the dtype conversion, mean and std scaling | |
| """ | |
| in_dtype = state.dtype | |
| latent_mean = self.latent_mean.to(in_dtype) | |
| latent_std = self.latent_std.to(in_dtype) | |
| encoded_state = self.encoder(state.to(self.dtype)) | |
| if isinstance(encoded_state, torch.Tensor): | |
| pass | |
| elif isinstance(encoded_state, tuple): | |
| assert isinstance(encoded_state[0], torch.Tensor) | |
| encoded_state = encoded_state[0] | |
| else: | |
| raise ValueError("Invalid type of encoded state") | |
| return (encoded_state.to(in_dtype) - latent_mean) / latent_std | |
| def decode(self, latent: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Decode the input latent to state; also handle the dtype conversion, mean and std scaling | |
| """ | |
| in_dtype = latent.dtype | |
| latent = latent * self.latent_std.to(in_dtype) + self.latent_mean.to(in_dtype) | |
| return self.decoder(latent.to(self.dtype)).to(in_dtype) | |
| def reset_dtype(self, *args, **kwargs): | |
| """ | |
| Resets the data type of the encoder and decoder to the model's default data type. | |
| Args: | |
| *args, **kwargs: Unused, present to allow flexibility in method calls. | |
| """ | |
| del args, kwargs | |
| self.decoder.to(self.dtype) | |
| self.encoder.to(self.dtype) | |
| class JITVAE(BasePretrainedImageVAE): | |
| """ | |
| A JIT compiled Variational Autoencoder (VAE) that loads pre-trained encoder | |
| and decoder components from a remote store, handles data type conversions, and normalization | |
| using provided mean and standard deviation values for latent space representation. | |
| Attributes: | |
| encoder (Module): The JIT compiled encoder loaded from storage. | |
| decoder (Module): The JIT compiled decoder loaded from storage. | |
| latent_mean (Tensor): The mean used for normalizing the latent representation. | |
| latent_std (Tensor): The standard deviation used for normalizing the latent representation. | |
| dtype (dtype): Data type for model tensors, determined by whether bf16 is enabled. | |
| Args: | |
| enc_fp (str): File path to the encoder's JIT file on the remote store. | |
| dec_fp (str): File path to the decoder's JIT file on the remote store. | |
| name (str): Name of the model, used for differentiating cache file paths. | |
| mean_std_fp (str): File path to the pickle file containing mean and std of the latent space. | |
| latent_ch (int, optional): Number of latent channels (default is 16). | |
| is_image (bool, optional): Flag to indicate whether the output is an image (default is True). | |
| is_bf16 (bool, optional): Flag to use Brain Floating Point 16-bit data type (default is True). | |
| """ | |
| def __init__( | |
| self, | |
| enc_fp: str, | |
| dec_fp: str, | |
| name: str, | |
| mean_std_fp: str, | |
| latent_ch: int = 16, | |
| is_image: bool = True, | |
| is_bf16: bool = True, | |
| ): | |
| super().__init__(name, mean_std_fp, latent_ch, is_image, is_bf16) | |
| self.load_encoder(enc_fp) | |
| self.load_decoder(dec_fp) | |
| def load_encoder(self, enc_fp: str) -> None: | |
| """ | |
| Load the encoder from the remote store. | |
| Args: | |
| - enc_fp (str): File path to the encoder's JIT file on the remote store. | |
| """ | |
| self.encoder = torch.jit.load(enc_fp, map_location="cuda") | |
| self.encoder.eval() | |
| for param in self.encoder.parameters(): | |
| param.requires_grad = False | |
| self.encoder.to(self.dtype) | |
| def load_decoder(self, dec_fp: str) -> None: | |
| """ | |
| Load the decoder from the remote store. | |
| Args: | |
| - dec_fp (str): File path to the decoder's JIT file on the remote store. | |
| """ | |
| self.decoder = torch.jit.load(dec_fp, map_location="cuda") | |
| self.decoder.eval() | |
| for param in self.decoder.parameters(): | |
| param.requires_grad = False | |
| self.decoder.to(self.dtype) | |
| class StateDictVAE(BasePretrainedImageVAE): | |
| """ | |
| A Variational Autoencoder (VAE) that loads pre-trained weights into | |
| provided encoder and decoder components from a remote store, handles data type conversions, | |
| and normalization using provided mean and standard deviation values for latent space representation. | |
| Attributes: | |
| encoder (Module): The encoder with weights loaded from storage. | |
| decoder (Module): The decoder with weights loaded from storage. | |
| latent_mean (Tensor): The mean used for normalizing the latent representation. | |
| latent_std (Tensor): The standard deviation used for normalizing the latent representation. | |
| dtype (dtype): Data type for model tensors, determined by whether bf16 is enabled. | |
| Args: | |
| enc_fp (str): File path to the encoder's JIT file on the remote store. | |
| dec_fp (str): File path to the decoder's JIT file on the remote store. | |
| vae (Module): Instance of VAE with not loaded weights | |
| name (str): Name of the model, used for differentiating cache file paths. | |
| mean_std_fp (str): File path to the pickle file containing mean and std of the latent space. | |
| latent_ch (int, optional): Number of latent channels (default is 16). | |
| is_image (bool, optional): Flag to indicate whether the output is an image (default is True). | |
| is_bf16 (bool, optional): Flag to use Brain Floating Point 16-bit data type (default is True). | |
| """ | |
| def __init__( | |
| self, | |
| enc_fp: str, | |
| dec_fp: str, | |
| vae: torch.nn.Module, | |
| name: str, | |
| mean_std_fp: str, | |
| latent_ch: int = 16, | |
| is_image: bool = True, | |
| is_bf16: bool = True, | |
| ): | |
| super().__init__(name, mean_std_fp, latent_ch, is_image, is_bf16) | |
| self.load_encoder_and_decoder(enc_fp, dec_fp, vae) | |
| def load_encoder_and_decoder(self, enc_fp: str, dec_fp: str, vae: torch.nn.Module) -> None: | |
| """ | |
| Load the encoder from the remote store. | |
| Args: | |
| - vae_fp (str): File path to the vae's state dict file on the remote store. | |
| - vae (str): VAE module into which weights will be loaded. | |
| """ | |
| state_dict_enc = load_from_s3_with_cache( | |
| enc_fp, | |
| f"vae/{self.name}_enc.jit", | |
| easy_io_kwargs={"map_location": torch.device(torch.cuda.current_device())}, | |
| backend_args=self.backend_args, | |
| ) | |
| state_dict_dec = load_from_s3_with_cache( | |
| dec_fp, | |
| f"vae/{self.name}_dec.jit", | |
| easy_io_kwargs={"map_location": torch.device(torch.cuda.current_device())}, | |
| backend_args=self.backend_args, | |
| ) | |
| jit_weights_state_dict = state_dict_enc.state_dict() | state_dict_dec.state_dict() | |
| jit_weights_state_dict = { | |
| k: v | |
| for k, v in jit_weights_state_dict.items() | |
| # Global variables captured by JIT | |
| if k | |
| not in ( | |
| "encoder.patcher.wavelets", | |
| "encoder.patcher._arange", | |
| "decoder.unpatcher.wavelets", | |
| "decoder.unpatcher._arange", | |
| ) | |
| } | |
| vae.load_state_dict(jit_weights_state_dict) | |
| vae.eval() | |
| for param in vae.parameters(): | |
| param.requires_grad = False | |
| vae.to(self.dtype) | |
| self.vae = vae | |
| self.encoder = self.vae.encode | |
| self.decoder = self.vae.decode | |
| def reset_dtype(self, *args, **kwargs): | |
| """ | |
| Resets the data type of the encoder and decoder to the model's default data type. | |
| Args: | |
| *args, **kwargs: Unused, present to allow flexibility in method calls. | |
| """ | |
| del args, kwargs | |
| self.vae.to(self.dtype) | |
| class ConfigBasedVideoVAE(BaseVAE): | |
| """VAE loaded from a tokenizer experiment config + training checkpoint. | |
| Works with any tokenizer that exposes `encode(x) -> (sample, (mu, logvar))` | |
| and `decode(z) -> x`, including WanVAE2P1 and TransformerVAE. | |
| Latent statistics can be supplied either via ``mean_std_fp`` (a saved | |
| ``(mean, std)`` tuple / ``{"mean", "std"}`` dict) or via inline | |
| ``latent_mean`` / ``latent_std`` lists. Inline values take precedence | |
| so callers that don't have a ``latent_stats.pt`` next to their | |
| checkpoint (e.g. the public ``Wan2.1_VAE.pth`` release) can pass the | |
| per-channel statistics directly from a constant. | |
| """ | |
| def __init__( | |
| self, | |
| experiment_name: str, | |
| checkpoint_path: str, | |
| mean_std_fp: str | None = None, | |
| latent_ch: int = 64, | |
| is_bf16: bool = False, | |
| spatial_compression: int = 8, | |
| latent_mean: list[float] | tuple[float, ...] | None = None, | |
| latent_std: list[float] | tuple[float, ...] | None = None, | |
| apply_validity_mask: bool = False, | |
| intensity_pad_value: float = 0.0, | |
| network_overrides: dict | None = None, | |
| use_ema: bool = False, | |
| per_frame: bool = False, | |
| encoder_is_bf16: bool | None = None, | |
| decoder_is_bf16: bool | None = None, | |
| strict_checkpoint: bool = False, | |
| ): | |
| super().__init__(channel=latent_ch, name=f"config_vae_{experiment_name}") | |
| from diffusion.training.module.vae_utils import ( | |
| build_vae_from_tokenizer_experiment, | |
| ) | |
| self.is_image = False | |
| self._spatial_compression = spatial_compression | |
| # per_frame: the wrapped net is a 2D (single-frame) tokenizer (cosmos CI, | |
| # LiDM) whose encode/decode take (B, C, H, W). encode/decode fold the clip's | |
| # T axis into the batch, run the net per frame, and reshape the latent back | |
| # to (B, Cz, T, Hz, Wz) so the rest of the video pipeline (5D latent-stat | |
| # buffers, DiT) is unchanged. The first-frame prepend still happens in the | |
| # dataset (is_video_tokenizer=True) -> T frames each encoded independently. | |
| self.per_frame = bool(per_frame) | |
| encoder_is_bf16 = ( | |
| is_bf16 if encoder_is_bf16 is None else encoder_is_bf16 | |
| ) | |
| decoder_is_bf16 = ( | |
| is_bf16 if decoder_is_bf16 is None else decoder_is_bf16 | |
| ) | |
| self.encoder_dtype = ( | |
| torch.bfloat16 if encoder_is_bf16 else torch.float32 | |
| ) | |
| self.decoder_dtype = ( | |
| torch.bfloat16 if decoder_is_bf16 else torch.float32 | |
| ) | |
| # Keep the legacy public dtype tied to decode. Encode paths use the | |
| # explicit encoder dtype below. | |
| self.dtype = self.decoder_dtype | |
| # ``network_overrides`` are applied AFTER the experiment's own network | |
| # overrides (e.g. ``utonia_align=False`` to skip building the training-only | |
| # VF/PTv3 teacher -- encode/decode is bit-identical and the leftover | |
| # ``utonia.*`` ckpt keys drop harmlessly under the strict=False load below). | |
| self.vae_net = build_vae_from_tokenizer_experiment( | |
| experiment_name, **(network_overrides or {}) | |
| ) | |
| # Validity-head support: predict_validity VAEs return a per-pixel raydrop | |
| # logit from decode(); when ``apply_validity_mask`` is set we fill the | |
| # predicted-invalid pixels with the depth sentinel so the decoded range | |
| # map carries raydrop directly (matches the trained 2-channel output). | |
| self.predict_validity = bool(getattr(self.vae_net, "predict_validity", False)) | |
| # decoder_validity_head: a SEPARATE additive head taps the decoder recon | |
| # and emits a raydrop logit via decode(z, return_validity=True) (does NOT | |
| # widen the main conv). Mutually exclusive with predict_validity. Handled | |
| # on the same masking path below. | |
| self.decoder_validity_head = bool(getattr(self.vae_net, "decoder_validity_head", False)) | |
| self.apply_validity_mask = bool(apply_validity_mask) | |
| self.invalid_fill = float(getattr(self.vae_net, "mask_input_min_value", -1.0)) | |
| # Fill value for DATA channels missing from the input (e.g. the intensity | |
| # channel when encoding a mono-depth hdmap through a [depth, intensity] | |
| # VAE). Intensity is normalized to [-1, 1]; a flat plane is OOD, so this | |
| # is exposed for A/B tuning. Default 0.0 preserves prior behavior. | |
| self.intensity_pad_value = float(intensity_pad_value) | |
| # ``is_ltx`` flags whether the wrapped VAE supports LTX-style | |
| # timestep-conditioned decoding (``decode(z, t=...)`` with a | |
| # rectified-flow t threading through AdaLN). Detected from the | |
| # underlying net's ``denoising_t_max`` attr -- the LTX | |
| # TransformerVAELTX sets it to > 0 when AdaLN is built. | |
| # Callers should gate any ``t`` they want to pass to | |
| # ``decode`` on this flag rather than guessing from the model | |
| # class or ``is_image``. | |
| self.is_ltx = bool(getattr(self.vae_net, "denoising_t_max", 0.0) > 0) | |
| ckpt = torch.load(checkpoint_path, map_location="cpu", weights_only=False) | |
| state = ckpt.get("model", ckpt) | |
| vae_state = {} | |
| for k, v in state.items(): | |
| for prefix in ("network.", "vae.", "model.network.", "model.vae."): | |
| if k.startswith(prefix): | |
| k = k[len(prefix):] | |
| break | |
| vae_state[k] = v | |
| from tokenizer.networks.transformers.transformer_vae_cross_dataset import ( | |
| CrossDatasetTransformerVAE, | |
| ) | |
| if isinstance(self.vae_net, CrossDatasetTransformerVAE): | |
| # Cross-dataset encode/decode regenerate this nonlearned grid at | |
| # the input resolution; its saved shape is not a model constraint. | |
| saved_coords = vae_state.pop("coords", None) | |
| if saved_coords is not None: | |
| print( | |
| "[ConfigBasedVideoVAE] ignoring derived cross-dataset " | |
| f"coords buffer {tuple(saved_coords.shape)}; " | |
| "coordinates are regenerated for each input grid" | |
| ) | |
| incompatible = self.vae_net.load_state_dict(vae_state, strict=False) | |
| if strict_checkpoint: | |
| allowed_missing = ( | |
| {"coords"} | |
| if isinstance(self.vae_net, CrossDatasetTransformerVAE) | |
| else set() | |
| ) | |
| missing = sorted(set(incompatible.missing_keys) - allowed_missing) | |
| unexpected = incompatible.unexpected_keys | |
| if missing or unexpected: | |
| raise RuntimeError( | |
| "Tokenizer checkpoint/config mismatch: " | |
| f"missing={missing[:12]}, unexpected={unexpected[:12]}. " | |
| "Refusing inference with partially loaded weights." | |
| ) | |
| print("[ConfigBasedVideoVAE] checkpoint/config match: all weights loaded") | |
| # Optional: overlay the EMA shadow weights bundled in the same tokenizer | |
| # ckpt (``ema.network-<name>`` where <name> is the param name with "." | |
| # replaced by "-"; see utils/ema.py). Only trainable params are EMA'd, so | |
| # buffers stay at their reg values -- hence overlay onto the reg-loaded net | |
| # with strict=False rather than a from-scratch load. NOTE: the diffusion | |
| # model was trained on reg-encoded latents, so this only swaps the DECODER | |
| # weights at inference -- an EMA-vs-reg decode A/B, not a latent-space change. | |
| if use_ema: | |
| ema_state = { | |
| k[len("ema.network-"):].replace("-", "."): v | |
| for k, v in state.items() | |
| if k.startswith("ema.network-") | |
| } | |
| if not ema_state: | |
| raise RuntimeError( | |
| f"use_ema=True but no 'ema.network-*' keys in {checkpoint_path} " | |
| "(EMA was not enabled for this tokenizer run)." | |
| ) | |
| _, unexpected = self.vae_net.load_state_dict(ema_state, strict=False) | |
| if unexpected: | |
| raise RuntimeError( | |
| f"EMA overlay had {len(unexpected)} unexpected keys, e.g. " | |
| f"{unexpected[:5]} -- ema->net name remap is wrong for this arch." | |
| ) | |
| print(f"[ConfigBasedVideoVAE] use_ema=True: overlaid {len(ema_state)} " | |
| "EMA params onto the decoder/vae_net") | |
| self.vae_net.eval() | |
| for p in self.vae_net.parameters(): | |
| p.requires_grad = False | |
| self._reset_module_dtypes() | |
| if latent_mean is not None and latent_std is not None: | |
| assert len(latent_mean) == latent_ch and len(latent_std) == latent_ch, ( | |
| f"Inline latent_mean/latent_std must have length latent_ch={latent_ch}, " | |
| f"got {len(latent_mean)}/{len(latent_std)}" | |
| ) | |
| latent_mean_t = torch.tensor(list(latent_mean), dtype=torch.float32) | |
| latent_std_t = torch.tensor(list(latent_std), dtype=torch.float32) | |
| else: | |
| assert mean_std_fp is not None, ( | |
| "ConfigBasedVideoVAE requires either mean_std_fp or " | |
| "(latent_mean, latent_std)." | |
| ) | |
| stats = torch.load(mean_std_fp, map_location="cpu", weights_only=False) | |
| if isinstance(stats, (tuple, list)): | |
| latent_mean_t, latent_std_t = stats | |
| else: | |
| latent_mean_t = stats["mean"] | |
| latent_std_t = stats["std"] | |
| shape = [1, latent_ch, 1, 1, 1] | |
| self.register_buffer( | |
| "latent_mean", | |
| latent_mean_t.to(self.dtype).reshape(*shape), | |
| persistent=False, | |
| ) | |
| self.register_buffer( | |
| "latent_std", | |
| latent_std_t.to(self.dtype).reshape(*shape), | |
| persistent=False, | |
| ) | |
| def _reset_module_dtypes(self) -> None: | |
| self.vae_net.to(self.decoder_dtype) | |
| if self.encoder_dtype == self.decoder_dtype: | |
| return | |
| if not all( | |
| hasattr(self.vae_net, name) for name in ("encoder", "quant_conv") | |
| ): | |
| raise ValueError( | |
| "mixed VAE precision requires separate encoder and " | |
| "quant_conv modules" | |
| ) | |
| self.vae_net.encoder.to(self.encoder_dtype) | |
| self.vae_net.quant_conv.to(self.encoder_dtype) | |
| def _prepare_encoder_input(self, state: torch.Tensor) -> torch.Tensor: | |
| x = state.to(self.encoder_dtype) | |
| # mask_as_input VAEs expect a [data..., mask] encoder input, but | |
| # vae_net.encode() does NOT build the mask (forward() does). The net | |
| # reconstructs ``out_channels`` DATA channels (1 = [depth] mono, 2 = | |
| # [depth, intensity]); build the full encoder input to match training: | |
| # * pad missing data channels with zeros (e.g. hdmap is mono depth even | |
| # for an intensity VAE -> [depth, 0(intensity)] before the mask), | |
| # * derive the validity mask from depth (ch0, same threshold as the | |
| # loss/forward) and concat it. | |
| # No-op for non-mask VAEs and for inputs that already carry the mask | |
| # channel (x.shape[1] == data_ch + 1). Bit-identical to the old 1ch path | |
| # for mono mask VAEs (out_channels == 1). | |
| if getattr(self.vae_net, "mask_as_input", False): | |
| data_ch = int(getattr(self.vae_net, "out_channels", 1)) | |
| if x.shape[1] <= data_ch: | |
| if x.shape[1] < data_ch: # pad missing data channels (intensity) | |
| pad = x.new_full( | |
| (x.shape[0], data_ch - x.shape[1]) + tuple(x.shape[2:]), | |
| self.intensity_pad_value) | |
| x = torch.cat([x, pad], dim=1) | |
| thr = self.vae_net.mask_input_min_value + self.vae_net.mask_input_eps | |
| valid = (x[:, :1] > thr).to(x.dtype) | |
| x = torch.cat([x, valid], dim=1) | |
| return x | |
| def encode( | |
| self, | |
| state: torch.Tensor, | |
| sensor_dataset_id: torch.Tensor | int | None = None, | |
| ) -> torch.Tensor: | |
| if sensor_dataset_id is not None: | |
| setter = getattr(self.vae_net, "set_dataset_id", None) | |
| if setter is not None: | |
| setter(sensor_dataset_id) | |
| elif getattr(self.vae_net, "uses_dataset_id", False): | |
| raise TypeError( | |
| f"{type(self.vae_net).__name__} requires a dataset ID but " | |
| "does not expose set_dataset_id()" | |
| ) | |
| x = self._prepare_encoder_input(state) | |
| if self.per_frame: | |
| # 2D net: (B, C, T, H, W) -> encode each frame as (B*T, C, H, W). | |
| B, C, T, H, W = x.shape | |
| frames = x.permute(0, 2, 1, 3, 4).reshape(B * T, C, H, W) | |
| enc = self.vae_net.encode(frames) | |
| z = enc[0] if isinstance(enc, tuple) else enc | |
| Cz, Hz, Wz = z.shape[1], z.shape[2], z.shape[3] | |
| latent = z.reshape(B, T, Cz, Hz, Wz).permute(0, 2, 1, 3, 4) | |
| else: | |
| encoded = self.vae_net.encode(x) | |
| latent = encoded[0] if isinstance(encoded, tuple) else encoded | |
| latent = latent.to(self.encoder_dtype) | |
| latent_mean = self.latent_mean.to(self.encoder_dtype) | |
| latent_std = self.latent_std.to(self.encoder_dtype) | |
| return (latent - latent_mean) / latent_std | |
| def encode_stream( | |
| self, | |
| state_new: torch.Tensor, | |
| temporal_kv_cache=None, | |
| max_cache_frames: int | None = None, | |
| ): | |
| """Encode newly appended frames with the wrapped causal VAE cache.""" | |
| if self.per_frame or not getattr( | |
| self.vae_net, "supports_encoder_temporal_kv_cache", False | |
| ): | |
| raise NotImplementedError( | |
| f"{type(self.vae_net).__name__} does not support temporal " | |
| "KV-cache encoding" | |
| ) | |
| x_new = self._prepare_encoder_input(state_new) | |
| encoded, new_cache = self.vae_net.encode_stream( | |
| x_new, | |
| temporal_kv_cache=temporal_kv_cache, | |
| max_cache_frames=max_cache_frames, | |
| ) | |
| latent = encoded[0] if isinstance(encoded, tuple) else encoded | |
| latent = latent.to(self.encoder_dtype) | |
| latent_mean = self.latent_mean.to(self.encoder_dtype) | |
| latent_std = self.latent_std.to(self.encoder_dtype) | |
| latent = (latent - latent_mean) / latent_std | |
| return latent, new_cache | |
| def decode( | |
| self, | |
| latent: torch.Tensor, | |
| t: float | torch.Tensor | None = None, | |
| return_validity: bool = False, | |
| sensor_dataset_id: torch.Tensor | int | None = None, | |
| ) -> torch.Tensor: | |
| """Decode a normalized latent back to pixels. | |
| Args: | |
| latent: ``(B, latent_ch, T, H, W)`` in normalized-latent space | |
| (i.e. already divided by ``self.sigma_data`` upstream). | |
| t: optional rectified-flow timestep in ``[0, 1]``. Only | |
| accepted when ``self.is_ltx`` is True (the wrapped VAE | |
| net supports ``decode(z, t=t)``). For non-LTX wrappers | |
| the standard ``decode(z)`` signature is used and | |
| passing ``t`` raises -- prevents silently dropping the | |
| timestep on an unrelated VAE. | |
| return_validity: when True (and the wrapped net has a validity | |
| head), also return the per-pixel validity probability | |
| ``(B, 1, T, H, W)`` in ``[0, 1]``; ``None`` otherwise. | |
| When the wrapped net has ``predict_validity`` and this wrapper was | |
| built with ``apply_validity_mask=True``, predicted-invalid pixels | |
| (prob < 0.5) are filled with the depth sentinel so the returned range | |
| map carries raydrop directly. | |
| """ | |
| if sensor_dataset_id is not None: | |
| setter = getattr(self.vae_net, "set_dataset_id", None) | |
| if setter is not None: | |
| setter(sensor_dataset_id) | |
| elif getattr(self.vae_net, "uses_dataset_id", False): | |
| raise TypeError( | |
| f"{type(self.vae_net).__name__} requires a dataset ID but " | |
| "does not expose set_dataset_id()" | |
| ) | |
| if t is not None and not self.is_ltx: | |
| raise ValueError( | |
| f"Got t={t!r} but the wrapped VAE net " | |
| f"({type(self.vae_net).__name__}) is not LTX-style " | |
| f"(is_ltx=False). Use a TransformerVAELTX checkpoint " | |
| f"with denoising_t_max > 0 to enable timestep-" | |
| f"conditioned decoding." | |
| ) | |
| latent = latent.to(self.decoder_dtype) | |
| latent_std = self.latent_std.to(self.decoder_dtype) | |
| latent_mean = self.latent_mean.to(self.decoder_dtype) | |
| latent = latent * latent_std + latent_mean | |
| if self.per_frame: | |
| # 2D net: (B, Cz, T, Hz, Wz) -> decode each frame as (B*T, Cz, Hz, Wz). | |
| # These nets have no validity head, so return_validity yields None. | |
| B, Cz, T, Hz, Wz = latent.shape | |
| z = latent.permute(0, 2, 1, 3, 4).reshape(B * T, Cz, Hz, Wz) | |
| rec = self.vae_net.decode(z) | |
| if isinstance(rec, tuple): | |
| rec = rec[0] | |
| if rec.ndim == 5: # some nets return (B, C, 1, H, W) | |
| rec = rec[:, :, 0] | |
| C, H, W = rec.shape[1], rec.shape[2], rec.shape[3] | |
| out = rec.reshape(B, T, C, H, W).permute(0, 2, 1, 3, 4) | |
| if return_validity: | |
| return out, None | |
| return out | |
| validity = None | |
| if self.is_ltx: | |
| out = self.vae_net.decode(latent, t=t) | |
| elif self.predict_validity or self.decoder_validity_head: | |
| out, logits = self.vae_net.decode(latent, return_validity=True) | |
| if logits is not None: | |
| validity = torch.sigmoid(logits.to(out.dtype)) | |
| if self.apply_validity_mask: | |
| out = out.masked_fill(validity < 0.5, self.invalid_fill) | |
| else: | |
| out = self.vae_net.decode(latent) | |
| if return_validity: | |
| return out, validity | |
| return out | |
| def decode_stream( | |
| self, | |
| latent_new: torch.Tensor, | |
| temporal_kv_cache=None, | |
| return_validity: bool = False, | |
| max_cache_frames: int | None = None, | |
| ): | |
| """Decode newly appended frames with the wrapped causal VAE cache.""" | |
| if self.per_frame or self.is_ltx or self.decoder_validity_head: | |
| raise NotImplementedError( | |
| "streaming decode currently supports the attention-based " | |
| "TransformerVAE video decoder with its integrated validity head" | |
| ) | |
| if not getattr( | |
| self.vae_net, "supports_temporal_kv_cache", False | |
| ): | |
| raise NotImplementedError( | |
| f"{type(self.vae_net).__name__} does not support temporal " | |
| "KV-cache decoding" | |
| ) | |
| latent_new = latent_new.to(self.decoder_dtype) | |
| latent_std = self.latent_std.to(self.decoder_dtype) | |
| latent_mean = self.latent_mean.to(self.decoder_dtype) | |
| latent_new = latent_new * latent_std + latent_mean | |
| decode_kwargs = { | |
| "temporal_kv_cache": temporal_kv_cache, | |
| "return_validity": self.predict_validity, | |
| } | |
| if max_cache_frames is not None: | |
| decode_kwargs["max_cache_frames"] = max_cache_frames | |
| decoded, new_cache = self.vae_net.decode_stream( | |
| latent_new, **decode_kwargs | |
| ) | |
| validity = None | |
| if self.predict_validity: | |
| out, logits = decoded | |
| validity = torch.sigmoid(logits.to(out.dtype)) | |
| if self.apply_validity_mask: | |
| out = out.masked_fill(validity < 0.5, self.invalid_fill) | |
| else: | |
| out = decoded | |
| if return_validity: | |
| return out, validity, new_cache | |
| return out, new_cache | |
| def reset_dtype(self, *args, **kwargs): | |
| del args, kwargs | |
| self._reset_module_dtypes() | |
| def spatial_compression_factor(self) -> int: | |
| return self._spatial_compression | |
| class SDVAE(BaseVAE): | |
| def __init__(self, batch_size=16, count_std: bool = False, is_downsample: bool = True) -> None: | |
| super().__init__(channel=4, name="sd_vae") | |
| self.dtype = torch.bfloat16 | |
| self.register_buffer( | |
| "scale", | |
| torch.tensor([4.17, 4.62, 3.71, 3.28], dtype=self.dtype).reciprocal().reshape(1, -1, 1, 1), | |
| persistent=False, | |
| ) | |
| self.register_buffer( | |
| "bias", | |
| -1.0 * torch.tensor([5.81, 3.25, 0.12, -2.15], dtype=self.dtype).reshape(1, -1, 1, 1) * self.scale, | |
| persistent=False, | |
| ) | |
| self.batch_size = batch_size | |
| self.count_std = count_std | |
| self.is_downsample = is_downsample | |
| self.load_vae() | |
| self.reset_dtype() | |
| def reset_dtype(self, *args, **kwargs): | |
| del args, kwargs | |
| self.vae.to(self.dtype) | |
| def load_vae(self) -> None: | |
| os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" | |
| os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1" | |
| import diffusers | |
| vae_name = "stabilityai/sd-vae-ft-mse" | |
| try: | |
| vae = diffusers.models.AutoencoderKL.from_pretrained(vae_name, local_files_only=True) | |
| except: # noqa: E722 | |
| # Could not load the model from cache; try without local_files_only. | |
| vae = diffusers.models.AutoencoderKL.from_pretrained(vae_name) | |
| self.vae = vae.eval().requires_grad_(False) | |
| def encode(self, state: torch.Tensor) -> torch.Tensor: | |
| """ | |
| state : pixel range [-1, 1] | |
| """ | |
| if self.is_downsample: | |
| _h, _w = state.shape[-2:] | |
| state = F.interpolate(state, size=(_h // 2, _w // 2), mode="bilinear", align_corners=False) | |
| in_dtype = state.dtype | |
| state = state.to(self.dtype) | |
| state = (state + 1.0) / 2.0 | |
| latent_dist = self.vae.encode(state)["latent_dist"] | |
| mean, std = latent_dist.mean, latent_dist.std | |
| if self.count_std: | |
| latent = mean + torch.randn_like(mean) * std | |
| else: | |
| latent = mean | |
| latent = latent * self.scale | |
| latent = latent + self.bias | |
| return latent.to(in_dtype) | |
| def decode(self, latent: torch.Tensor) -> torch.Tensor: | |
| in_dtype = latent.dtype | |
| latent = latent.to(self.dtype) | |
| latent = latent - self.bias | |
| latent = latent / self.scale | |
| latent = torch.cat([self.vae.decode(batch)["sample"] for batch in latent.split(self.batch_size)]) | |
| if self.is_downsample: | |
| _h, _w = latent.shape[-2:] | |
| latent = F.interpolate(latent, size=(_h * 2, _w * 2), mode="bilinear", align_corners=False) | |
| return latent.to(in_dtype) * 2 - 1.0 | |
| def spatial_compression_factor(self) -> int: | |
| return 8 | |