Spaces:
Running on Zero
Running on Zero
| # Scale-RAE Representation AutoEncoder tokenizer. | |
| # | |
| # Pairs the frozen SigLIP-2 So400M (`google/siglip2-so400m-patch14-224`) encoder | |
| # with the Scale-RAE paper's ViT-XL decoder | |
| # (https://huggingface.co/nyu-visionx/Scale-RAE-Qwen1.5B_DiT2.4B). This mirrors | |
| # the existing DINOv2-based RAE tokenizer (`dinov2_vae.py`) but for the SigLIP-2 | |
| # feature space the Scale-RAE T2I diffusion model targets. | |
| # | |
| # Shape contract (16-multiple I/O matching dinov2_vae conventions; the latent | |
| # grid is Scale-RAE's native 16×16 / 256-token / patch-14 layout): | |
| # Input : (B, 3, 1, H, W) pixel range [-1, 1] (H = W = 256) | |
| # Latent : (B, 1152, 1, 16, 16) (virtual 16x compression) | |
| # Output : (B, 3, 1, 256, 256) pixel range [-1, 1] — only when decoder weights | |
| # are supplied; otherwise decode() returns zeros. | |
| # | |
| # Scale-RAE was natively trained at 224 input → 16×16 token grid (patch 14). | |
| # We bridge the 14-multiple internal grid with the pipeline's 16-multiple | |
| # interface using two bicubic interpolations at the pixel boundary, so both | |
| # encoder and decoder run IN DISTRIBUTION on the 16×16 / 224-pixel grid: | |
| # | |
| # encode: 256 (16×16) → bicubic ↓ → 224 (14×16) → SigLIP-2 patch=14 → 16×16 grid | |
| # decode: 16×16 grid → SigLIP-2 decoder patch=14 → 224 (14×16) → bicubic ↑ → 256 | |
| # | |
| # Internal pipeline per encode(): | |
| # 1. [-1,1] -> [0,1] | |
| # 2. bicubic ↓ resize H,W -> 224,224 (with antialias=True) | |
| # 3. SigLIP normalize using the encoder's image_mean/std (typically 0.5/0.5) | |
| # 4. SigLIP-2 vision_tower forward -> (B, 256, 1152) last-hidden tokens | |
| # 5. F.layer_norm(.., (1152,), eps=1e-6) — affine-free layer norm matches the | |
| # Scale-RAE feature space the decoder + diffusion DiT were trained on | |
| # 6. reshape (B, 256, 1152) -> (B, 1152, 16, 16) | |
| # | |
| # Internal pipeline per decode(): | |
| # 1. (B, 1152, 16, 16) -> (B, 256, 1152) | |
| # 2. prepend zero CLS -> (B, 257, 1152) | |
| # 3. GeneralDecoder forward (drop_cls_token=True) -> (B, 256, 14*14*3) | |
| # 4. unpatchify with 16×16 grid -> (B, 3, 224, 224) in SigLIP-normalized space | |
| # 5. denormalize via image_std/mean -> [0, 1] | |
| # 6. bicubic ↑ resize 224 -> 256 (the "14→16 multiple bridge") | |
| # 7. map to [-1, 1] | |
| # | |
| # Architectural knobs (mirroring inference.py:siglip2-so400m-web73m): | |
| # pretrained_path = "google/siglip2-so400m-patch14-224" | |
| # resize_target = 224 (encoder internal, 14-multiple) | |
| # native_spatial_resolution = 256 (interface, 16-multiple) | |
| # decoder_out_pixel_size = 224 (decoder raw output, 14-multiple) | |
| # final_pixel_size = 256 (after 14→16 multiple bridge) | |
| # pretrained_decoder_path = checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt | |
| # decoder_config_path = checkpoints/scale_rae/decoder/XL_decoder_config.json | |
| import json | |
| import math | |
| import os | |
| from contextlib import nullcontext | |
| from typing import Optional | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from pid._ext.imaginaire.lazy_config import LazyCall as L | |
| from pid._ext.imaginaire.lazy_config import LazyDict | |
| from pid._ext.imaginaire.utils import log | |
| from pid._ext.imaginaire.utils.distributed import get_rank, rank0_first, sync_model_states | |
| from pid._src.tokenizers.interface import VideoTokenizerInterface | |
| from pid._src.tokenizers.scale_rae_decoder import GeneralDecoder | |
| __all__ = [ | |
| "ScaleRAEEncoder", | |
| "ScaleRAEVAE", | |
| "ScaleRAEVAEInterface", | |
| "ScaleRAEConfig", | |
| ] | |
| # =========================================================================== | |
| # Layer 1 — SigLIP-2 encoder + (optional) Scale-RAE ViT-XL decoder | |
| # =========================================================================== | |
| class ScaleRAEEncoder(nn.Module): | |
| """Frozen SigLIP-2 So400M encoder (+ optional frozen Scale-RAE decoder). | |
| Args: | |
| pretrained_path: HF repo id or local directory | |
| (default ``google/siglip2-so400m-patch14-224``). | |
| pretrained_decoder_path: path to the Scale-RAE ViT-XL decoder weights | |
| (e.g. ``siglip2_sop14_i224_web73M_ganw3_decXL.pt``). If None the | |
| decoder stays None and decode() is a no-op. | |
| decoder_config_path: path to the decoder JSON config (e.g. | |
| ``XL_decoder_config.json``). Required when ``pretrained_decoder_path`` | |
| is given. | |
| decoder_num_patches: token grid size the decoder was trained with | |
| (16*16 = 256 for SigLIP-2 So400M @ 224). | |
| """ | |
| def __init__( | |
| self, | |
| pretrained_path: str = "google/siglip2-so400m-patch14-224", | |
| pretrained_decoder_path: Optional[str] = None, | |
| decoder_config_path: Optional[str] = None, | |
| decoder_num_patches: int = 256, | |
| ): | |
| super().__init__() | |
| os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1") | |
| os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1") | |
| from transformers import AutoImageProcessor, AutoModel | |
| from transformers.models.vit_mae.configuration_vit_mae import ViTMAEConfig | |
| try: | |
| full_model = AutoModel.from_pretrained(pretrained_path, local_files_only=True) | |
| except (OSError, ValueError, AttributeError): | |
| full_model = AutoModel.from_pretrained(pretrained_path) | |
| # SigLIP wraps a vision_model + text_model; we only need the vision tower. | |
| vision_tower = getattr(full_model, "vision_model", full_model) | |
| vision_tower.requires_grad_(False) | |
| vision_tower.eval() | |
| # Bypass the SigLIP-2 post_layernorm: the Scale-RAE decoder was trained | |
| # against the *pre*-final-LN hidden state (with an affine-free LN | |
| # applied externally — see Scale-RAE's `SigLIPEncoderForDebugging`, | |
| # multimodal_decoder/__init__.py:80-93). On transformers 4.57+ | |
| # `Siglip2VisionModel.forward` no longer surfaces hidden_states even | |
| # when `output_hidden_states=True` is passed, so the only way to reach | |
| # the pre-LN tensor is to neutralize `post_layernorm` itself. | |
| if hasattr(vision_tower, "post_layernorm"): | |
| vision_tower.post_layernorm = nn.Identity() | |
| self.encoder = vision_tower | |
| # patch14 + 224 input -> 16x16 = 256 tokens; hidden_size = 1152. | |
| vis_cfg = getattr(full_model.config, "vision_config", full_model.config) | |
| self.patch_size = int(vis_cfg.patch_size) | |
| self.hidden_size = int(vis_cfg.hidden_size) | |
| # SigLIP image mean/std (0.5/0.5 for siglip2-so400m). Read from the | |
| # processor to avoid hard-coding. Non-persistent — not saved to ckpt. | |
| try: | |
| proc = AutoImageProcessor.from_pretrained(pretrained_path, local_files_only=True) | |
| except (OSError, ValueError, AttributeError): | |
| proc = AutoImageProcessor.from_pretrained(pretrained_path) | |
| mean = torch.tensor(proc.image_mean, dtype=torch.float32).view(1, 3, 1, 1) | |
| std = torch.tensor(proc.image_std, dtype=torch.float32).view(1, 3, 1, 1) | |
| self.register_buffer("siglip_mean", mean, persistent=False) | |
| self.register_buffer("siglip_std", std, persistent=False) | |
| # Optional Scale-RAE decoder. | |
| self.decoder: Optional[GeneralDecoder] = None | |
| if pretrained_decoder_path is not None: | |
| if decoder_config_path is None: | |
| raise ValueError("decoder_config_path is required when pretrained_decoder_path is set") | |
| with open(decoder_config_path) as f: | |
| cfg_dict = json.load(f) | |
| # Match Scale-RAE/multimodal_decoder/__init__.py: hidden_size is the | |
| # encoder's vision hidden dim (1152 for SigLIP-2 So400M). | |
| cfg_dict["hidden_size"] = self.hidden_size | |
| cfg = ViTMAEConfig(**cfg_dict) | |
| decoder = GeneralDecoder(cfg, num_patches=decoder_num_patches) | |
| log.info(f"Loading Scale-RAE decoder weights from {pretrained_decoder_path}") | |
| state_dict = torch.load(pretrained_decoder_path, map_location="cpu") | |
| missing, unexpected = decoder.load_state_dict(state_dict, strict=False) | |
| if missing: | |
| log.warning(f"Scale-RAE decoder missing keys: {missing}") | |
| if unexpected: | |
| log.warning(f"Scale-RAE decoder unexpected keys: {unexpected}") | |
| decoder.requires_grad_(False) | |
| decoder.eval() | |
| self.decoder = decoder | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| """x: (B, 3, H, W) already SigLIP-normalized. | |
| Returns the pre-final-LN patch tokens (B, N_patches, hidden_size). | |
| We neutralize `vision_model.post_layernorm` at __init__ time, so | |
| ``out.last_hidden_state`` IS the pre-LN tensor that Scale-RAE's | |
| decoder was trained against. The ScaleRAEVAE wrapper then applies | |
| an affine-free `F.layer_norm` to match `SigLIPEncoderForDebugging`. | |
| """ | |
| out = self.encoder(x) | |
| return out.last_hidden_state | |
| # =========================================================================== | |
| # Layer 2 — ScaleRAEVAE wrapper (dtype/AMP + resize + preprocess + decode) | |
| # =========================================================================== | |
| _DECODE_WARNED = {"v": False} | |
| def _build_scale_rae_encoder( | |
| pretrained_path: str, | |
| pretrained_decoder_path: Optional[str], | |
| decoder_config_path: Optional[str], | |
| decoder_num_patches: int, | |
| ) -> ScaleRAEEncoder: | |
| log.info( | |
| f"Loading Scale-RAE encoder from {pretrained_path} (decoder={pretrained_decoder_path}) on rank {get_rank()}" | |
| ) | |
| return ScaleRAEEncoder( | |
| pretrained_path=pretrained_path, | |
| pretrained_decoder_path=pretrained_decoder_path, | |
| decoder_config_path=decoder_config_path, | |
| decoder_num_patches=decoder_num_patches, | |
| ) | |
| class ScaleRAEVAE: | |
| """Dtype/AMP wrapper around ScaleRAEEncoder + Scale-RAE decoder. | |
| All tensors are 4D (B, C, H, W). | |
| encode(images): (B,3,H,W) in [-1,1] → (B, 1152, H/spatial_compression, W/spatial_compression) | |
| decode(zs) : (B, 1152, h, w) → (B,3,H_out,W_out) in [-1,1] when a | |
| decoder is loaded, otherwise a zeros tensor. | |
| """ | |
| def __init__( | |
| self, | |
| pretrained_path: str = "google/siglip2-so400m-patch14-224", | |
| resize_target: int = 224, | |
| spatial_compression_factor: int = 16, | |
| dtype: torch.dtype = torch.bfloat16, | |
| device: str = "cuda", | |
| is_amp: bool = False, | |
| pretrained_decoder_path: Optional[str] = None, | |
| decoder_config_path: Optional[str] = None, | |
| decoder_num_patches: int = 256, | |
| decoder_out_pixel_size: int = 224, | |
| final_pixel_size: int = 256, | |
| ): | |
| self.dtype = dtype | |
| self.device = device | |
| self.resize_target = resize_target | |
| self.spatial_compression_factor = spatial_compression_factor | |
| self.decoder_out_pixel_size = decoder_out_pixel_size | |
| self.final_pixel_size = final_pixel_size | |
| self.model = _build_scale_rae_encoder( | |
| pretrained_path=pretrained_path, | |
| pretrained_decoder_path=pretrained_decoder_path, | |
| decoder_config_path=decoder_config_path, | |
| decoder_num_patches=decoder_num_patches, | |
| ) | |
| self.model = self.model.to(device).eval().requires_grad_(False) | |
| sync_model_states(self.model) | |
| self.has_decoder = self.model.decoder is not None | |
| self.is_amp = is_amp | |
| if not is_amp: | |
| self.model = self.model.to(dtype=dtype) | |
| self.context = nullcontext() | |
| else: | |
| self.context = torch.amp.autocast("cuda", dtype=dtype) | |
| def count_param(self): | |
| return sum(p.numel() for p in self.model.parameters()) | |
| def encode(self, images: torch.Tensor) -> torch.Tensor: | |
| """images: (B, 3, H, W) in [-1, 1]. | |
| Returns (B, 1152, H/spatial_compression, W/spatial_compression). | |
| """ | |
| assert images.ndim == 4, f"ScaleRAEVAE.encode expects 4D input, got {images.shape}" | |
| in_dtype = images.dtype | |
| B, _, H, W = images.shape | |
| x = (images + 1.0) / 2.0 # [-1,1] → [0,1] | |
| if H != self.resize_target or W != self.resize_target: | |
| # Downsample (e.g. 256 → 224 = 16 × patch_size 14). antialias=True | |
| # adds the recommended low-pass filter for bicubic downsampling | |
| # (PyTorch ≥ 1.11) — avoids moire when shrinking to a non-integer | |
| # ratio. The 16→14 multiple bridge on the encoder side. | |
| x = F.interpolate( | |
| x.to(torch.float32), | |
| size=(self.resize_target, self.resize_target), | |
| mode="bicubic", | |
| align_corners=False, | |
| antialias=True, | |
| ) | |
| mean = self.model.siglip_mean.to(x.device, x.dtype) | |
| std = self.model.siglip_std.to(x.device, x.dtype) | |
| x = (x - mean) / std | |
| with self.context: | |
| if not self.is_amp: | |
| x = x.to(self.dtype) | |
| tokens = self.model(x) # (B, N_patches, hidden_size) | |
| # Affine-free layer norm — matches Scale-RAE feature space. | |
| tokens = F.layer_norm(tokens.float(), (self.model.hidden_size,), weight=None, bias=None, eps=1e-6) | |
| N, C = tokens.shape[1], tokens.shape[2] | |
| h = int(math.isqrt(N)) | |
| assert h * h == N, f"SigLIP-2 patch count {N} is not a perfect square" | |
| z = tokens.transpose(1, 2).contiguous().view(B, C, h, h) | |
| return z.to(in_dtype) | |
| def decode(self, zs: torch.Tensor) -> torch.Tensor: | |
| """zs: (B, 1152, h, w). Returns (B, 3, final_pixel_size, final_pixel_size) in [-1, 1]. | |
| If the decoder was not loaded, returns zeros of the expected shape. | |
| """ | |
| if not self.has_decoder: | |
| if not _DECODE_WARNED["v"]: | |
| log.warning("ScaleRAEVAE.decode is a no-op stub; no decoder weights loaded.") | |
| _DECODE_WARNED["v"] = True | |
| B, _, h, w = zs.shape | |
| H = h * self.spatial_compression_factor | |
| W = w * self.spatial_compression_factor | |
| return torch.zeros((B, 3, H, W), device=zs.device, dtype=zs.dtype) | |
| in_dtype = zs.dtype | |
| B, C, h, w = zs.shape | |
| intermediate = self.decoder_out_pixel_size | |
| # (B, C, h, w) → (B, h*w, C); prepend zero CLS to (B, h*w+1, C). | |
| tokens = zs.reshape(B, C, h * w).transpose(1, 2) | |
| cls = torch.zeros((B, 1, C), device=zs.device, dtype=zs.dtype) | |
| tokens = torch.cat([cls, tokens], dim=1) | |
| with self.context: | |
| if not self.is_amp: | |
| tokens = tokens.to(self.dtype) | |
| decoder_out = self.model.decoder(tokens, drop_cls_token=True) | |
| logits = decoder_out.logits # (B, 256, p*p*3) | |
| pixels = self.model.decoder.unpatchify( | |
| logits, | |
| original_image_size=(intermediate, intermediate), | |
| ) # (B, 3, intermediate, intermediate) in SigLIP-normalized space | |
| # De-SigLIP-normalize to [0,1]. | |
| pixels = pixels.to(torch.float32) | |
| mean = self.model.siglip_mean.to(pixels.device, pixels.dtype) | |
| std = self.model.siglip_std.to(pixels.device, pixels.dtype) | |
| pixels = (pixels * std + mean).clamp(0.0, 1.0) | |
| # 14-multiple → 16-multiple bridge: bicubic upsample (e.g. 224 → 256). | |
| # antialias is irrelevant for upsampling (no aliasing introduced). | |
| if pixels.shape[-1] != self.final_pixel_size: | |
| pixels = F.interpolate( | |
| pixels, | |
| size=(self.final_pixel_size, self.final_pixel_size), | |
| mode="bicubic", | |
| align_corners=False, | |
| ).clamp(0.0, 1.0) | |
| # [0,1] → [-1,1]. | |
| pixels = pixels * 2.0 - 1.0 | |
| return pixels.to(in_dtype) | |
| # =========================================================================== | |
| # Layer 3 — ScaleRAEVAEInterface(VideoTokenizerInterface) | |
| # =========================================================================== | |
| class ScaleRAEVAEInterface(VideoTokenizerInterface): | |
| """Pipeline-compatible Scale-RAE: SigLIP-2 So400M encoder + ViT-XL decoder. | |
| Image-only (T=1). Default I/O at 256×256 (16-multiple, virtual 16x | |
| compression to a 16×16 token grid) — unified with dinov2_vae and the | |
| pixel-diffusion pipeline. Internally bridges Scale-RAE's native | |
| 14-multiple resolution (224 = 16×14) with two bicubic interpolations: | |
| encode-side downsample (256→224, antialias) and decode-side upsample | |
| (224→256). Both encoder and decoder run IN DISTRIBUTION on the pretrained | |
| 16×16 token grid. | |
| """ | |
| def __init__( | |
| self, | |
| chunk_duration: int = 1, | |
| pretrained_path: str = "google/siglip2-so400m-patch14-224", | |
| resize_target: int = 224, | |
| native_spatial_resolution: int = 256, | |
| pretrained_decoder_path: Optional[str] = None, | |
| decoder_config_path: Optional[str] = None, | |
| decoder_num_patches: int = 256, | |
| decoder_out_pixel_size: int = 224, | |
| final_pixel_size: int = 256, | |
| **kwargs, | |
| ): | |
| del kwargs # absorb LazyDict metadata (e.g. "name") | |
| self.model = ScaleRAEVAE( | |
| pretrained_path=pretrained_path, | |
| resize_target=resize_target, | |
| spatial_compression_factor=16, | |
| dtype=torch.bfloat16, | |
| is_amp=False, | |
| pretrained_decoder_path=pretrained_decoder_path, | |
| decoder_config_path=decoder_config_path, | |
| decoder_num_patches=decoder_num_patches, | |
| decoder_out_pixel_size=decoder_out_pixel_size, | |
| final_pixel_size=final_pixel_size, | |
| ) | |
| self.chunk_duration = chunk_duration | |
| self._spatial_resolution = native_spatial_resolution | |
| def dtype(self): | |
| return self.model.dtype | |
| def reset_dtype(self): | |
| pass | |
| def encode(self, state: torch.Tensor) -> torch.Tensor: | |
| if state.ndim == 5: | |
| assert state.shape[2] == 1, f"Image-only VAE requires T=1, got T={state.shape[2]}" | |
| x = state.squeeze(2) | |
| else: | |
| x = state | |
| latent = self.model.encode(x) # (B, 1152, h, w) | |
| return latent.unsqueeze(2) # (B, 1152, 1, h, w) | |
| def decode(self, latent: torch.Tensor) -> torch.Tensor: | |
| if latent.ndim == 5: | |
| assert latent.shape[2] == 1, f"Image-only VAE requires T=1, got T={latent.shape[2]}" | |
| z = latent.squeeze(2) | |
| else: | |
| z = latent | |
| recon = self.model.decode(z) # (B, 3, H, W) | |
| return recon.unsqueeze(2) # (B, 3, 1, H, W) | |
| def get_latent_num_frames(self, num_pixel_frames: int) -> int: | |
| return num_pixel_frames | |
| def get_pixel_num_frames(self, num_latent_frames: int) -> int: | |
| return num_latent_frames | |
| def spatial_compression_factor(self): | |
| return 16 | |
| def temporal_compression_factor(self): | |
| return 1 | |
| def pixel_chunk_duration(self): | |
| return self.chunk_duration | |
| def latent_chunk_duration(self): | |
| return self.chunk_duration | |
| def latent_ch(self): | |
| return 1152 | |
| def spatial_resolution(self): | |
| return self._spatial_resolution | |
| def name(self): | |
| return "scale_rae_tokenizer" | |
| # =========================================================================== | |
| # Layer 4 — LazyDict config | |
| # =========================================================================== | |
| # Encoder-only configuration (no decoder, e.g. for diffusion training that only | |
| # needs the latent target and decodes at evaluation time). 256 input → 16×16 | |
| # latent grid at 1152-D (16-multiple I/O, virtual 16x spatial compression). | |
| ScaleRAEEncoderOnlyConfig: LazyDict = L(ScaleRAEVAEInterface)( | |
| name="scale_rae_encoder_only_tokenizer", | |
| pretrained_path="google/siglip2-so400m-patch14-224", | |
| resize_target=224, | |
| native_spatial_resolution=256, | |
| ) | |
| # Full Scale-RAE at 256×256 I/O (SigLIP-2 So400M encoder + ViT-XL decoder, | |
| # web73M). Both encoder and decoder run IN DISTRIBUTION on Scale-RAE's | |
| # pretrained 16×16 / 224-pixel grid; bicubic interpolations bridge to the | |
| # pipeline's 16-multiple 256-pixel boundary at I/O. | |
| ScaleRAEConfig: LazyDict = L(ScaleRAEVAEInterface)( | |
| name="scale_rae_tokenizer", | |
| pretrained_path="google/siglip2-so400m-patch14-224", | |
| resize_target=224, | |
| native_spatial_resolution=256, | |
| pretrained_decoder_path="checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt", | |
| decoder_config_path="checkpoints/scale_rae/decoder/XL_decoder_config.json", | |
| decoder_num_patches=256, | |
| decoder_out_pixel_size=224, | |
| final_pixel_size=256, | |
| ) | |