Spaces:
Running on Zero
Running on Zero
Download uranus/functional/vae_decoder.py from hugging-apps/uranus-embodied-world-model: direct link, hf CLI and curl.
- Browser
- Download file 4.53 kB
-
https://huggingface.co/spaces/hugging-apps/uranus-embodied-world-model/resolve/main/uranus/functional/vae_decoder.py
- Command line
-
hf download hf://spaces/hugging-apps/uranus-embodied-world-model/uranus/functional/vae_decoder.py
-
curl -L -o vae_decoder.py https://huggingface.co/spaces/hugging-apps/uranus-embodied-world-model/resolve/main/uranus/functional/vae_decoder.py
4.53 kB
| """ | |
| Functional API for VAE decoding of video latents to pixel frames. | |
| """ | |
| import inspect | |
| import torch | |
| from einops import rearrange | |
| from uranus.modules.vae import VideoVAE38_, WanVideoVAE, count_conv3d, unpatchify | |
| def decode_latents( | |
| vae: WanVideoVAE, | |
| latents: torch.Tensor, # [B, N_CAM, z_dim, F_lat, H_lat, W_lat] | |
| device: torch.device, | |
| tiled: bool = True, | |
| tile_size: tuple[int, int] = (34, 34), | |
| tile_stride: tuple[int, int] = (18, 16), | |
| ) -> torch.Tensor: | |
| """ | |
| VAE-decode latent frames to video frames. | |
| VAE temporal expansion: | |
| - 1st latent frame → 1 video frame | |
| - subsequent latent frames → 4 video frames each | |
| Args: | |
| vae: WanVideoVAE instance | |
| latents: Video latents [B, N_CAM, z_dim, F_lat, H_lat, W_lat] | |
| device: Target device | |
| tiled: Whether to use tiled VAE decoding | |
| tile_size: Tile size in latent space | |
| tile_stride: Tile stride in latent space | |
| Returns: | |
| Video frames [B, N_CAM, 3, F_video, H_pix, W_pix] in [-1, 1] | |
| """ | |
| batch, n_cam, _, _, _, _ = latents.shape | |
| latents_flat = rearrange(latents, "B N_CAM C F H W -> (B N_CAM) C F H W") | |
| videos = vae.decode( | |
| latents_flat, | |
| device=device, | |
| tiled=tiled, | |
| tile_size=tile_size, | |
| tile_stride=tile_stride, | |
| ) | |
| videos = rearrange( | |
| videos, "(B N_CAM) C F H W -> B N_CAM C F H W", B=batch, N_CAM=n_cam | |
| ) | |
| return videos.clamp_(-1, 1) | |
| def _apply_vae_scale(z: torch.Tensor, vae: WanVideoVAE) -> torch.Tensor: | |
| scale = vae.scale | |
| if isinstance(scale[0], torch.Tensor): | |
| scale = [s.to(dtype=z.dtype, device=z.device) for s in scale] | |
| return z / scale[1].view(1, vae.z_dim, 1, 1, 1) + scale[0].view( | |
| 1, vae.z_dim, 1, 1, 1 | |
| ) | |
| scale = scale.to(dtype=z.dtype, device=z.device) | |
| return z / scale[1] + scale[0] | |
| def _decode_single_chunk( | |
| vae: WanVideoVAE, | |
| latents: torch.Tensor, | |
| dec_feat_map, | |
| decoded_latent_frames: int, | |
| ) -> tuple[torch.Tensor, list, list]: | |
| model = vae.model | |
| z = _apply_vae_scale(latents, vae) | |
| x = model.conv2(z) | |
| # Flash-VAED decoder exposes its own cache-slot count (its causal convs | |
| # are depthwise-separable); fall back to the legacy count otherwise. | |
| count_fn = getattr(model.decoder, "count_conv3d", None) or count_conv3d | |
| if dec_feat_map is None: | |
| dec_feat_map = [None] * count_fn(model.decoder) | |
| decoder_signature = inspect.signature(model.decoder.forward) | |
| supports_first_chunk = "first_chunk" in decoder_signature.parameters | |
| outputs = [] | |
| for frame_idx in range(x.shape[2]): | |
| feat_idx = [0] | |
| decoder_kwargs = { | |
| "feat_cache": dec_feat_map, | |
| "feat_idx": feat_idx, | |
| } | |
| if supports_first_chunk: | |
| decoder_kwargs["first_chunk"] = ( | |
| decoded_latent_frames == 0 and frame_idx == 0 | |
| ) | |
| out, dec_feat_map, feat_idx = model.decoder( | |
| x[:, :, frame_idx : frame_idx + 1], **decoder_kwargs | |
| ) | |
| outputs.append(out) | |
| video = torch.cat(outputs, dim=2) if len(outputs) > 1 else outputs[0] | |
| if isinstance(model, VideoVAE38_): | |
| video = unpatchify(video, patch_size=2) | |
| return video, dec_feat_map, feat_idx | |
| def decode_latents_chunk( | |
| vae: WanVideoVAE, | |
| latents: torch.Tensor, | |
| device: torch.device, | |
| dec_feat_map=None, | |
| dec_feat_idx: list | None = None, | |
| decoded_latent_frames: int = 0, | |
| tiled: bool = False, | |
| tile_size: tuple[int, int] = (34, 34), | |
| tile_stride: tuple[int, int] = (18, 16), | |
| ) -> tuple[torch.Tensor, list, list, int]: | |
| """ | |
| Incrementally decode a latent chunk while reusing decoder feature cache. | |
| This mirrors `VideoVAE_.decode()` / `VideoVAE38_.decode()` but externalizes | |
| decoder cache so streaming callers can carry it in their own state. | |
| """ | |
| del tiled, tile_size, tile_stride | |
| batch, n_cam, _, f_lat, _, _ = latents.shape | |
| latents_flat = rearrange(latents, "B N_CAM C F H W -> (B N_CAM) C F H W").to( | |
| device=device | |
| ) | |
| video, dec_feat_map, dec_feat_idx = _decode_single_chunk( | |
| vae=vae, | |
| latents=latents_flat, | |
| dec_feat_map=dec_feat_map, | |
| decoded_latent_frames=decoded_latent_frames, | |
| ) | |
| videos = rearrange( | |
| video, "(B N_CAM) C F H W -> B N_CAM C F H W", B=batch, N_CAM=n_cam | |
| ) | |
| return ( | |
| videos.clamp_(-1, 1), | |
| dec_feat_map, | |
| dec_feat_idx, | |
| decoded_latent_frames + f_lat, | |
| ) | |