Download 14B/code/video_fd/encoders/vjepa2_adapter.py from Elastic-forcing/Elastic-forcing-code: direct link, hf CLI and curl.
- Browser
- Download file 22.5 kB
-
https://huggingface.co/Elastic-forcing/Elastic-forcing-code/resolve/main/14B/code/video_fd/encoders/vjepa2_adapter.py
- Command line
-
hf download hf://Elastic-forcing/Elastic-forcing-code/14B/code/video_fd/encoders/vjepa2_adapter.py
-
curl -L -o vjepa2_adapter.py https://huggingface.co/Elastic-forcing/Elastic-forcing-code/resolve/main/14B/code/video_fd/encoders/vjepa2_adapter.py
22.5 kB
| """V-JEPA2 sparse and intermediate-dense video encoder adapter. | |
| The historical sparse API remains available. The dense API additionally | |
| returns the complete post-residual hidden state of an intermediate encoder | |
| block, before V-JEPA2's final LayerNorm and predictor. It is intended for | |
| feature-distribution losses whose gradient must replay through preprocessing | |
| and a frozen encoder prefix. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Dict, Optional, Sequence, Tuple | |
| import torch | |
| import torch.nn as nn | |
| from torch.utils.checkpoint import checkpoint | |
| from video_fd.encoders.base_sparse_video_encoder import ( | |
| SparseVideoEncoderAdapter, | |
| differentiable_resize_crop, | |
| ) | |
| class VJEPA2Adapter(SparseVideoEncoderAdapter): | |
| """Hugging Face V-JEPA2 adapter with an optional memory-pruned dense mode. | |
| Dense-mode configuration keys: | |
| * ``dense_layer_index``: zero-based complete encoder block (default 15) | |
| * ``dense_num_frames``: contiguous input frames (default 64) | |
| * ``resize_short_side``: differentiable resize short edge (default 292) | |
| * ``dense_only``: discard predictor, final norm, and blocks after the dense | |
| target once the checkpoint is loaded | |
| ``model`` is an injection seam for tiny unit tests. Production callers | |
| normally omit it and load ``model_id`` with ``from_pretrained``. | |
| """ | |
| def __init__(self, config, model: Optional[nn.Module] = None): | |
| config = dict(config) | |
| config.setdefault("name", "vjepa2") | |
| config.setdefault("input_size", config.get("crop_size", 256)) | |
| config.setdefault("input_frames", 16) | |
| config.setdefault("norm_mean", (0.485, 0.456, 0.406)) | |
| config.setdefault("norm_std", (0.229, 0.224, 0.225)) | |
| super().__init__(config) | |
| self.model_id = str(config.get("model_id", "assets/models/vjepa2-vitl-fpc64-256")) | |
| self.allow_temporal_pos_interpolation = bool(config.get("allow_temporal_pos_interpolation", True)) | |
| self.tubelet_size = int(config.get("tubelet_size", 2)) | |
| self.patch_size = int(config.get("patch_size", 16)) | |
| self.dense_layer_index = int(config.get("dense_layer_index", 15)) | |
| self.dense_num_frames = int(config.get("dense_num_frames", 64)) | |
| self.resize_short_side = int( | |
| config.get("resize_short_side", config.get("dense_resize_short_side", 292)) | |
| ) | |
| self.dense_only = bool(config.get("dense_only", False)) | |
| if self.dense_num_frames <= 0: | |
| raise ValueError( | |
| f"dense_num_frames must be positive, got {self.dense_num_frames}" | |
| ) | |
| if self.resize_short_side < self.input_size: | |
| raise ValueError( | |
| "resize_short_side must be >= input_size, got " | |
| f"{self.resize_short_side} < {self.input_size}" | |
| ) | |
| self._model = None | |
| self._dense_pruned = False | |
| if model is None: | |
| self._load() | |
| else: | |
| self._adopt_model(model) | |
| def _load(self): | |
| model_dir = Path(self.model_id).expanduser() | |
| if not model_dir.is_dir(): | |
| raise FileNotFoundError(f"Prepare the local V-JEPA2 model directory: {model_dir}") | |
| self.model_id = str(model_dir.resolve()) | |
| try: | |
| from transformers import VJEPA2Model # type: ignore | |
| except Exception as exc: # pragma: no cover | |
| raise ImportError( | |
| "V-JEPA2 requires `transformers` with VJEPA2Model. Original error: %r" % (exc,) | |
| ) from exc | |
| model = VJEPA2Model.from_pretrained(self.model_id, local_files_only=True) | |
| self._adopt_model(model) | |
| def _adopt_model(self, model: nn.Module) -> None: | |
| """Install a loaded model, derive its geometry, optionally prune it.""" | |
| cfg = getattr(model, "config", None) | |
| if cfg is not None: | |
| self.patch_size = int(getattr(cfg, "patch_size", self.patch_size)) | |
| self.tubelet_size = int(getattr(cfg, "tubelet_size", self.tubelet_size)) | |
| self.input_size = int(getattr(cfg, "crop_size", getattr(cfg, "image_size", self.input_size))) | |
| cfg_frames = getattr(cfg, "frames_per_clip", None) | |
| if cfg_frames is not None and int(cfg_frames) != self.input_frames and not self.allow_temporal_pos_interpolation: | |
| print(f"[vjepa2] checkpoint frames_per_clip={cfg_frames} != {self.input_frames}; " | |
| "temporal interpolation disabled -> will fall back to global pooling.") | |
| self._model = model.to(torch.float32) | |
| self._validate_dense_layer(self.dense_layer_index) | |
| if self.dense_only: | |
| self.prune_for_dense_prefix(self.dense_layer_index) | |
| self.freeze() | |
| def _grid_shape(self, n_tokens: int) -> Optional[Tuple[int, int, int]]: | |
| Tp = self.input_frames // self.tubelet_size | |
| Hp = self.input_size // self.patch_size | |
| Wp = self.input_size // self.patch_size | |
| return (Tp, Hp, Wp) if Tp * Hp * Wp == n_tokens else None | |
| def _encode_tokens(self, pixel_values: torch.Tensor) -> torch.Tensor: | |
| if self._dense_pruned: | |
| raise RuntimeError( | |
| "forward_sparse is unavailable after prune_for_dense_prefix(); " | |
| "construct VJEPA2Adapter with dense_only=False to retain it" | |
| ) | |
| # transformers VJEPA2 expects pixel_values_videos [B, T, 3, H, W] | |
| # Predictor output is unused by the sparse encoder and can be skipped | |
| # without changing encoder.last_hidden_state. | |
| out = self._model(pixel_values_videos=pixel_values, skip_predictor=True) | |
| tokens = out.last_hidden_state if hasattr(out, "last_hidden_state") else out[0] | |
| return tokens # [B, N, D] | |
| # -- intermediate dense feature extraction ----------------------------- | |
| def dense_feature_dim(self) -> int: | |
| cfg = getattr(self._model, "config", None) | |
| if cfg is None or not hasattr(cfg, "hidden_size"): | |
| raise RuntimeError("loaded V-JEPA2 model does not expose config.hidden_size") | |
| return int(cfg.hidden_size) | |
| def dense_raw_tokens(self) -> int: | |
| return ( | |
| (self.dense_num_frames // self.tubelet_size) | |
| * (self.input_size // self.patch_size) | |
| * (self.input_size // self.patch_size) | |
| ) | |
| def retained_dense_blocks(self) -> int: | |
| return len(self._model.encoder.layer) | |
| def _integer_index_tensor( | |
| values: torch.Tensor, | |
| *, | |
| name: str, | |
| device: torch.device, | |
| ) -> torch.Tensor: | |
| indices = torch.as_tensor(values) | |
| if ( | |
| indices.dtype == torch.bool | |
| or indices.is_floating_point() | |
| or indices.is_complex() | |
| ): | |
| raise TypeError(f"{name} must contain only integers") | |
| return indices.to(device=device, dtype=torch.long) | |
| def _temporal_window_indices( | |
| self, | |
| video_clip_01: torch.Tensor, | |
| *, | |
| temporal_window_starts: Optional[torch.Tensor], | |
| temporal_window_indices: Optional[torch.Tensor], | |
| ) -> Optional[torch.Tensor]: | |
| """Validate and canonicalize optional per-video contiguous windows.""" | |
| if ( | |
| temporal_window_starts is not None | |
| and temporal_window_indices is not None | |
| ): | |
| raise ValueError( | |
| "provide either temporal_window_starts or " | |
| "temporal_window_indices, not both" | |
| ) | |
| if ( | |
| temporal_window_starts is None | |
| and temporal_window_indices is None | |
| ): | |
| return None | |
| batch_size = int(video_clip_01.shape[0]) | |
| total_frames = int(video_clip_01.shape[1]) | |
| max_start = total_frames - self.dense_num_frames | |
| device = video_clip_01.device | |
| if temporal_window_starts is not None: | |
| starts = self._integer_index_tensor( | |
| temporal_window_starts, | |
| name="temporal_window_starts", | |
| device=device, | |
| ) | |
| if starts.ndim != 1 or int(starts.shape[0]) != batch_size: | |
| raise ValueError( | |
| "temporal_window_starts must have shape [B], got " | |
| f"{tuple(starts.shape)} for B={batch_size}" | |
| ) | |
| if starts.numel() and ( | |
| int(starts.min()) < 0 or int(starts.max()) > max_start | |
| ): | |
| raise ValueError( | |
| "temporal_window_starts must be in " | |
| f"[0,{max_start}]" | |
| ) | |
| offsets = torch.arange( | |
| self.dense_num_frames, | |
| dtype=torch.long, | |
| device=device, | |
| ) | |
| return starts[:, None] + offsets[None, :] | |
| indices = self._integer_index_tensor( | |
| temporal_window_indices, | |
| name="temporal_window_indices", | |
| device=device, | |
| ) | |
| expected_shape = (batch_size, self.dense_num_frames) | |
| if tuple(indices.shape) != expected_shape: | |
| raise ValueError( | |
| "temporal_window_indices must have shape " | |
| f"[B,{self.dense_num_frames}], got {tuple(indices.shape)} " | |
| f"for B={batch_size}" | |
| ) | |
| if indices.numel(): | |
| starts = indices[:, 0] | |
| if int(starts.min()) < 0 or int(starts.max()) > max_start: | |
| raise ValueError( | |
| "temporal_window_indices must describe windows starting " | |
| f"in [0,{max_start}]" | |
| ) | |
| offsets = torch.arange( | |
| self.dense_num_frames, | |
| dtype=torch.long, | |
| device=device, | |
| ) | |
| expected = starts[:, None] + offsets[None, :] | |
| if not bool(torch.equal(indices, expected)): | |
| raise ValueError( | |
| "each temporal_window_indices row must be a contiguous " | |
| "strictly increasing window" | |
| ) | |
| return indices | |
| def preprocess_dense( | |
| self, | |
| video_clip_01: torch.Tensor, | |
| temporal_window_starts: Optional[torch.Tensor] = None, | |
| temporal_window_indices: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| """Official differentiable V-JEPA2 preprocessing. | |
| The input is ``[B,T,3,H,W]`` in ``[0,1]``. By default a centered | |
| contiguous ``dense_num_frames`` window is selected. Callers may | |
| instead supply explicit integer starts with shape ``[B]`` or explicit | |
| contiguous indices with shape ``[B,dense_num_frames]``. Sampling is | |
| intentionally external: this adapter never consumes ambient RNG | |
| state. The short side is then resized to 292 (configurable), a 256 | |
| center crop is taken, and ImageNet normalization is applied. The | |
| returned layout remains ``[B,T,3,256,256]``. | |
| """ | |
| if video_clip_01.ndim != 5: | |
| raise ValueError( | |
| "video_clip_01 must have shape [B,T,3,H,W], " | |
| f"got {tuple(video_clip_01.shape)}" | |
| ) | |
| if video_clip_01.shape[2] != 3: | |
| raise ValueError( | |
| f"video_clip_01 must have 3 channels, got {video_clip_01.shape[2]}" | |
| ) | |
| total_frames = int(video_clip_01.shape[1]) | |
| if total_frames < self.dense_num_frames: | |
| raise ValueError( | |
| f"V-JEPA2 dense mode needs at least {self.dense_num_frames} frames, " | |
| f"got {total_frames}" | |
| ) | |
| frame_indices = self._temporal_window_indices( | |
| video_clip_01, | |
| temporal_window_starts=temporal_window_starts, | |
| temporal_window_indices=temporal_window_indices, | |
| ) | |
| if frame_indices is None: | |
| start = (total_frames - self.dense_num_frames) // 2 | |
| clip = video_clip_01.narrow(1, start, self.dense_num_frames) | |
| else: | |
| gather_indices = frame_indices[:, :, None, None, None].expand( | |
| -1, | |
| -1, | |
| int(video_clip_01.shape[2]), | |
| int(video_clip_01.shape[3]), | |
| int(video_clip_01.shape[4]), | |
| ) | |
| clip = torch.gather(video_clip_01, 1, gather_indices) | |
| x = differentiable_resize_crop( | |
| clip, | |
| self.input_size, | |
| crop_mode="center", | |
| resize_short_side=self.resize_short_side, | |
| interpolation="bilinear", | |
| round_center_crop=True, | |
| ) | |
| B, T, C, H, W = x.shape | |
| flat = x.reshape(B * T, C, H, W) | |
| flat = (flat - self.norm_mean.to(flat.dtype)) / self.norm_std.to(flat.dtype) | |
| return flat.reshape(B, T, C, H, W) | |
| def forward_dense( | |
| self, | |
| video_clip_01: torch.Tensor, | |
| layer_index: Optional[int] = None, | |
| token_indices: Optional[torch.Tensor] = None, | |
| gradient_checkpointing: bool = False, | |
| temporal_window_starts: Optional[torch.Tensor] = None, | |
| temporal_window_indices: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| """Return a raw intermediate dense token matrix from an input video. | |
| No final encoder LayerNorm, predictor, pooling, or flattening is applied. | |
| Gradients are retained with respect to ``video_clip_01`` even though all | |
| V-JEPA2 parameters are frozen. | |
| """ | |
| pixel_values = self.preprocess_dense( | |
| video_clip_01, | |
| temporal_window_starts=temporal_window_starts, | |
| temporal_window_indices=temporal_window_indices, | |
| ) | |
| return self.encode_dense_preprocessed( | |
| pixel_values, | |
| layer_index=layer_index, | |
| token_indices=token_indices, | |
| gradient_checkpointing=gradient_checkpointing, | |
| ) | |
| def forward_dense_layers( | |
| self, | |
| video_clip_01: torch.Tensor, | |
| layer_indices: Sequence[int], | |
| token_indices: Optional[torch.Tensor] = None, | |
| gradient_checkpointing: bool = False, | |
| temporal_window_starts: Optional[torch.Tensor] = None, | |
| temporal_window_indices: Optional[torch.Tensor] = None, | |
| ) -> Dict[int, torch.Tensor]: | |
| """Return several raw intermediate token matrices in one prefix pass. | |
| The encoder is executed exactly once through ``max(layer_indices)``. | |
| Each returned value is the complete post-residual output of its | |
| zero-based block, before V-JEPA2's final LayerNorm. The dictionary | |
| follows the caller-provided layer order. | |
| """ | |
| pixel_values = self.preprocess_dense( | |
| video_clip_01, | |
| temporal_window_starts=temporal_window_starts, | |
| temporal_window_indices=temporal_window_indices, | |
| ) | |
| return self.encode_dense_preprocessed_layers( | |
| pixel_values, | |
| layer_indices=layer_indices, | |
| token_indices=token_indices, | |
| gradient_checkpointing=gradient_checkpointing, | |
| ) | |
| def encode_dense_preprocessed( | |
| self, | |
| pixel_values: torch.Tensor, | |
| layer_index: Optional[int] = None, | |
| token_indices: Optional[torch.Tensor] = None, | |
| gradient_checkpointing: bool = False, | |
| ) -> torch.Tensor: | |
| """Run embeddings and complete blocks ``0..layer_index``. | |
| ``pixel_values`` must already have V-JEPA2 layout and normalization. | |
| This lower-level entrypoint lets replay code avoid preprocessing twice. | |
| """ | |
| target_layer = self.dense_layer_index if layer_index is None else int(layer_index) | |
| return self.encode_dense_preprocessed_layers( | |
| pixel_values, | |
| layer_indices=(target_layer,), | |
| token_indices=token_indices, | |
| gradient_checkpointing=gradient_checkpointing, | |
| )[target_layer] | |
| def encode_dense_preprocessed_layers( | |
| self, | |
| pixel_values: torch.Tensor, | |
| layer_indices: Sequence[int], | |
| token_indices: Optional[torch.Tensor] = None, | |
| gradient_checkpointing: bool = False, | |
| ) -> Dict[int, torch.Tensor]: | |
| """Run one encoder prefix and return all requested block outputs. | |
| ``pixel_values`` must already have V-JEPA2 layout and normalization. | |
| Blocks are evaluated only once, through the largest requested | |
| zero-based layer. Token selection happens as each requested layer is | |
| captured so that later blocks do not retain unneeded token outputs. | |
| """ | |
| if pixel_values.ndim != 5: | |
| raise ValueError( | |
| "pixel_values must have shape [B,T,3,H,W], " | |
| f"got {tuple(pixel_values.shape)}" | |
| ) | |
| if pixel_values.shape[2] != 3: | |
| raise ValueError(f"pixel_values must have 3 channels, got {pixel_values.shape[2]}") | |
| if pixel_values.shape[1] != self.dense_num_frames: | |
| raise ValueError( | |
| f"pixel_values must contain {self.dense_num_frames} frames, " | |
| f"got {pixel_values.shape[1]}" | |
| ) | |
| if tuple(pixel_values.shape[-2:]) != (self.input_size, self.input_size): | |
| raise ValueError( | |
| f"pixel_values must be {self.input_size}x{self.input_size}, " | |
| f"got {tuple(pixel_values.shape[-2:])}" | |
| ) | |
| try: | |
| requested_layers = tuple(int(layer) for layer in layer_indices) | |
| except TypeError as exc: | |
| raise TypeError("layer_indices must be a sequence of integers") from exc | |
| if not requested_layers: | |
| raise ValueError("layer_indices must not be empty") | |
| if len(set(requested_layers)) != len(requested_layers): | |
| raise ValueError("layer_indices must contain unique indices") | |
| for target_layer in requested_layers: | |
| self._validate_dense_layer(target_layer) | |
| max_target_layer = max(requested_layers) | |
| if max_target_layer >= self.retained_dense_blocks: | |
| raise ValueError( | |
| f"layer_index {max_target_layer} was pruned; retained blocks are " | |
| f"0..{self.retained_dense_blocks - 1}" | |
| ) | |
| encoder = self._model.encoder | |
| hidden_states = encoder.embeddings(pixel_values) | |
| expected_tokens = self.dense_raw_tokens | |
| if hidden_states.shape[1] != expected_tokens: | |
| raise RuntimeError( | |
| "unexpected V-JEPA2 dense token grid: expected " | |
| f"{expected_tokens}, got {hidden_states.shape[1]}" | |
| ) | |
| self._raw_dim = int(hidden_states.shape[-1]) | |
| indices = None | |
| if token_indices is not None: | |
| indices = torch.as_tensor(token_indices, device=hidden_states.device) | |
| if indices.ndim != 1: | |
| raise ValueError( | |
| f"token_indices must be 1-D, got shape {tuple(indices.shape)}" | |
| ) | |
| if indices.numel() == 0: | |
| raise ValueError("token_indices must not be empty") | |
| if indices.dtype != torch.long: | |
| indices = indices.to(torch.long) | |
| if int(indices.min()) < 0 or int(indices.max()) >= hidden_states.shape[1]: | |
| raise IndexError( | |
| f"token_indices must be in [0,{hidden_states.shape[1] - 1}]" | |
| ) | |
| requested_set = set(requested_layers) | |
| captured: Dict[int, torch.Tensor] = {} | |
| for block_index in range(max_target_layer + 1): | |
| block = encoder.layer[block_index] | |
| def run_block(x: torch.Tensor, module=block) -> torch.Tensor: | |
| return module( | |
| x, | |
| position_mask=None, | |
| head_mask=None, | |
| output_attentions=False, | |
| )[0] | |
| if ( | |
| gradient_checkpointing | |
| and torch.is_grad_enabled() | |
| and hidden_states.requires_grad | |
| ): | |
| hidden_states = checkpoint( | |
| run_block, | |
| hidden_states, | |
| use_reentrant=False, | |
| ) | |
| else: | |
| hidden_states = run_block(hidden_states) | |
| if block_index in requested_set: | |
| # This is deliberately before encoder.layernorm: | |
| # HF hidden_states[i+1] is the complete post-residual result | |
| # of zero-based block i. | |
| captured[block_index] = ( | |
| hidden_states | |
| if indices is None | |
| else hidden_states.index_select(1, indices) | |
| ) | |
| return {layer: captured[layer] for layer in requested_layers} | |
| def prune_for_dense_prefix(self, layer_index: Optional[int] = None): | |
| """Irreversibly retain only the modules required by dense-prefix mode.""" | |
| target_layer = self.dense_layer_index if layer_index is None else int(layer_index) | |
| self._validate_dense_layer(target_layer) | |
| if self._dense_pruned and target_layer >= len(self._model.encoder.layer): | |
| raise ValueError( | |
| f"cannot restore pruned layer {target_layer}; only " | |
| f"{len(self._model.encoder.layer)} blocks remain" | |
| ) | |
| self._model.encoder.layer = nn.ModuleList( | |
| list(self._model.encoder.layer[: target_layer + 1]) | |
| ) | |
| # Dense prefix never executes either module. Replacing them with None | |
| # unregisters their parameters immediately, which matters before FSDP. | |
| self._model.encoder.layernorm = None | |
| self._model.predictor = None | |
| self._dense_pruned = True | |
| self.dense_only = True | |
| self.dense_layer_index = target_layer | |
| return self | |
| def _validate_dense_layer(self, layer_index: int) -> None: | |
| if layer_index < 0: | |
| raise ValueError(f"dense layer index must be non-negative, got {layer_index}") | |
| encoder = getattr(self._model, "encoder", None) | |
| layers = getattr(encoder, "layer", None) | |
| if layers is None: | |
| raise RuntimeError("loaded V-JEPA2 model does not expose encoder.layer") | |
| configured_layers = int( | |
| getattr(getattr(self._model, "config", None), "num_hidden_layers", len(layers)) | |
| ) | |
| if layer_index >= configured_layers: | |
| raise ValueError( | |
| f"dense layer index {layer_index} out of range for " | |
| f"{configured_layers} encoder blocks" | |
| ) | |