Download 14B/code/video_fd/encoders/videomae_v2_adapter.py from Elastic-forcing/Elastic-forcing-code: direct link, hf CLI and curl.
- Browser
- Download file 26.4 kB
-
https://huggingface.co/Elastic-forcing/Elastic-forcing-code/resolve/main/14B/code/video_fd/encoders/videomae_v2_adapter.py
- Command line
-
hf download hf://Elastic-forcing/Elastic-forcing-code/14B/code/video_fd/encoders/videomae_v2_adapter.py
-
curl -L -o videomae_v2_adapter.py https://huggingface.co/Elastic-forcing/Elastic-forcing-code/resolve/main/14B/code/video_fd/encoders/videomae_v2_adapter.py
26.4 kB
| """OpenGVLab VideoMAE V2 sparse and intermediate-dense adapter. | |
| The OpenGVLab Hugging Face checkpoints use remote code and wrap their | |
| ``VisionTransformer`` in ``model.model``. That implementation consumes | |
| ``[B, C, T, H, W]`` and returns a pooled video embedding from its public | |
| forward method. Feature-distribution training instead needs the complete | |
| post-residual token state of an encoder block, before mean pooling and | |
| ``fc_norm``. This adapter therefore executes the verified OpenGVLab backbone | |
| prefix explicitly. | |
| There is deliberately no VideoMAE-v1 fallback: silently changing model family | |
| would make reference-bank identity and token geometry incorrect. | |
| """ | |
| from __future__ import annotations | |
| from collections.abc import Mapping, Sequence | |
| from numbers import Integral | |
| from pathlib import Path | |
| from typing import Dict, Optional, 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 VideoMAEv2Adapter(SparseVideoEncoderAdapter): | |
| """Frozen OpenGVLab VideoMAE V2 adapter with a dense-prefix API. | |
| Dense mode handles one native checkpoint segment (normally 16 frames) per | |
| call. Five-segment assembly is intentionally owned by the caller so five | |
| separately placed/frozen encoder instances can be used by the training | |
| model. | |
| ``model`` is an injection seam for tiny tests. Injected models must expose | |
| the same contract as the OpenGVLab remote-code wrapper: nested | |
| ``config.model_config`` and a ``model`` backbone with ``patch_embed``, | |
| ``pos_embed``, ``pos_drop``, and ``blocks``. | |
| """ | |
| def __init__(self, config, model: Optional[nn.Module] = None): | |
| config = dict(config) | |
| explicit_dense_num_frames = "dense_num_frames" in config | |
| explicit_resize_short_side = ( | |
| "resize_short_side" in config | |
| or "dense_resize_short_side" in config | |
| ) | |
| config.setdefault("name", "videomae_v2") | |
| config.setdefault("input_size", config.get("crop_size", 224)) | |
| 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/VideoMAEv2-Large") | |
| ) | |
| self.patch_size = int(config.get("patch_size", 16)) | |
| self.tubelet_size = int(config.get("tubelet_size", 2)) | |
| self.dense_layer_index = int(config.get("dense_layer_index", 15)) | |
| self.dense_num_frames = int(config.get("dense_num_frames", 16)) | |
| self.resize_short_side = int( | |
| config.get( | |
| "resize_short_side", | |
| config.get("dense_resize_short_side", self.input_size), | |
| ) | |
| ) | |
| self.dense_only = bool(config.get("dense_only", False)) | |
| self._explicit_dense_num_frames = explicit_dense_num_frames | |
| self._explicit_resize_short_side = explicit_resize_short_side | |
| self._model: Optional[nn.Module] = None | |
| self._configured_depth = 0 | |
| self._embed_dim = 0 | |
| self._dense_pruned = False | |
| if model is None: | |
| self._load() | |
| else: | |
| self._adopt_model(model) | |
| # -- loading and verified OpenGVLab architecture ---------------------- | |
| def _load(self) -> None: | |
| model_dir = Path(self.model_id).expanduser() | |
| if not model_dir.is_dir(): | |
| raise FileNotFoundError(f"Prepare the local VideoMAEv2 model directory: {model_dir}") | |
| self.model_id = str(model_dir.resolve()) | |
| try: | |
| from transformers import AutoModel # type: ignore | |
| except Exception as exc: # pragma: no cover - optional dependency | |
| raise ImportError( | |
| "OpenGVLab VideoMAE V2 requires transformers.AutoModel with " | |
| "trust_remote_code support" | |
| ) from exc | |
| load_kwargs: Dict[str, object] = {"trust_remote_code": True} | |
| for key in ("revision", "cache_dir"): | |
| if key in self.config: | |
| load_kwargs[key] = self.config[key] | |
| load_kwargs["local_files_only"] = True | |
| try: | |
| model = AutoModel.from_pretrained(self.model_id, **load_kwargs) | |
| except Exception as exc: # pragma: no cover - needs real checkpoint | |
| raise RuntimeError( | |
| f"failed to load OpenGVLab VideoMAE V2 checkpoint " | |
| f"{self.model_id!r}; no VideoMAE-v1 fallback is permitted" | |
| ) from exc | |
| self._adopt_model(model) | |
| def _config_value(model_config: object, name: str) -> object: | |
| if isinstance(model_config, Mapping): | |
| if name not in model_config: | |
| raise RuntimeError( | |
| f"OpenGVLab config.model_config is missing {name!r}" | |
| ) | |
| return model_config[name] | |
| if not hasattr(model_config, name): | |
| raise RuntimeError( | |
| f"OpenGVLab config.model_config is missing {name!r}" | |
| ) | |
| return getattr(model_config, name) | |
| def _positive_int(value: object, name: str) -> int: | |
| if isinstance(value, bool): | |
| raise RuntimeError(f"OpenGVLab {name} must be a positive integer") | |
| if isinstance(value, (tuple, list)): | |
| if len(value) != 2 or int(value[0]) != int(value[1]): | |
| raise RuntimeError( | |
| f"OpenGVLab {name} must describe a square geometry, got {value!r}" | |
| ) | |
| value = value[0] | |
| try: | |
| result = int(value) | |
| except (TypeError, ValueError) as exc: | |
| raise RuntimeError( | |
| f"OpenGVLab {name} must be a positive integer, got {value!r}" | |
| ) from exc | |
| if result <= 0: | |
| raise RuntimeError( | |
| f"OpenGVLab {name} must be positive, got {result}" | |
| ) | |
| return result | |
| def _adopt_model(self, model: nn.Module) -> None: | |
| cfg = getattr(model, "config", None) | |
| model_config = getattr(cfg, "model_config", None) | |
| if model_config is None: | |
| raise RuntimeError( | |
| "model is not an OpenGVLab VideoMAE V2 wrapper: expected " | |
| "config.model_config (VideoMAE-v1 models are unsupported)" | |
| ) | |
| patch_size = self._positive_int( | |
| self._config_value(model_config, "patch_size"), | |
| "model_config.patch_size", | |
| ) | |
| tubelet_size = self._positive_int( | |
| self._config_value(model_config, "tubelet_size"), | |
| "model_config.tubelet_size", | |
| ) | |
| input_size = self._positive_int( | |
| self._config_value(model_config, "img_size"), | |
| "model_config.img_size", | |
| ) | |
| input_frames = self._positive_int( | |
| self._config_value(model_config, "num_frames"), | |
| "model_config.num_frames", | |
| ) | |
| embed_dim = self._positive_int( | |
| self._config_value(model_config, "embed_dim"), | |
| "model_config.embed_dim", | |
| ) | |
| depth = self._positive_int( | |
| self._config_value(model_config, "depth"), | |
| "model_config.depth", | |
| ) | |
| backbone = getattr(model, "model", None) | |
| if not isinstance(backbone, nn.Module): | |
| raise RuntimeError( | |
| "OpenGVLab VideoMAE V2 wrapper must expose an nn.Module at model.model" | |
| ) | |
| for name in ("patch_embed", "pos_drop", "blocks"): | |
| if not hasattr(backbone, name): | |
| raise RuntimeError( | |
| "OpenGVLab VideoMAE V2 backbone is missing " | |
| f"model.{name}" | |
| ) | |
| blocks = getattr(backbone, "blocks") | |
| if not isinstance(blocks, (nn.ModuleList, nn.Sequential)): | |
| raise RuntimeError( | |
| "OpenGVLab VideoMAE V2 model.blocks must be a ModuleList or Sequential" | |
| ) | |
| if len(blocks) != depth: | |
| raise RuntimeError( | |
| "OpenGVLab VideoMAE V2 depth mismatch: config.model_config.depth=" | |
| f"{depth}, but model.blocks has {len(blocks)} entries" | |
| ) | |
| if not hasattr(backbone, "pos_embed"): | |
| raise RuntimeError( | |
| "OpenGVLab VideoMAE V2 backbone is missing model.pos_embed" | |
| ) | |
| if input_frames % tubelet_size: | |
| raise RuntimeError( | |
| f"num_frames={input_frames} is not divisible by tubelet_size={tubelet_size}" | |
| ) | |
| if input_size % patch_size: | |
| raise RuntimeError( | |
| f"img_size={input_size} is not divisible by patch_size={patch_size}" | |
| ) | |
| if ( | |
| self._explicit_dense_num_frames | |
| and self.dense_num_frames != input_frames | |
| ): | |
| raise ValueError( | |
| "dense_num_frames must equal the native OpenGVLab checkpoint " | |
| f"length ({input_frames}), got {self.dense_num_frames}" | |
| ) | |
| self.patch_size = patch_size | |
| self.tubelet_size = tubelet_size | |
| self.input_size = input_size | |
| self.input_frames = input_frames | |
| self.dense_num_frames = input_frames | |
| self._embed_dim = embed_dim | |
| self._configured_depth = depth | |
| if not self._explicit_resize_short_side: | |
| self.resize_short_side = input_size | |
| if self.resize_short_side < self.input_size: | |
| raise ValueError( | |
| "resize_short_side must be >= the checkpoint crop size, got " | |
| f"{self.resize_short_side} < {self.input_size}" | |
| ) | |
| self._model = model.to(dtype=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 backbone(self) -> nn.Module: | |
| if self._model is None: | |
| raise RuntimeError("VideoMAE V2 model is not loaded") | |
| backbone = getattr(self._model, "model", None) | |
| if not isinstance(backbone, nn.Module): | |
| raise RuntimeError("VideoMAE V2 backbone is unavailable") | |
| return backbone | |
| # -- geometry --------------------------------------------------------- | |
| def dense_feature_dim(self) -> int: | |
| return self._embed_dim | |
| 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.backbone.blocks) | |
| def _grid_shape(self, n_tokens: int) -> Optional[Tuple[int, int, int]]: | |
| temporal = self.input_frames // self.tubelet_size | |
| spatial = self.input_size // self.patch_size | |
| return ( | |
| (temporal, spatial, spatial) | |
| if temporal * spatial * spatial == n_tokens | |
| else None | |
| ) | |
| # -- official differentiable preprocessing --------------------------- | |
| def _preprocess_clip( | |
| self, | |
| video_clip_01: torch.Tensor, | |
| *, | |
| crop_mode: str, | |
| ) -> torch.Tensor: | |
| 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 int(video_clip_01.shape[2]) != 3: | |
| raise ValueError( | |
| f"video_clip_01 must have 3 channels, got {video_clip_01.shape[2]}" | |
| ) | |
| if int(video_clip_01.shape[1]) != self.input_frames: | |
| raise ValueError( | |
| "VideoMAE V2 requires exactly one native segment of " | |
| f"{self.input_frames} frames, got {video_clip_01.shape[1]}" | |
| ) | |
| x = differentiable_resize_crop( | |
| video_clip_01, | |
| self.input_size, | |
| crop_mode=crop_mode, | |
| resize_short_side=self.resize_short_side, | |
| interpolation="bilinear", | |
| round_center_crop=False, | |
| ) | |
| batch, frames, channels, height, width = x.shape | |
| flat = x.reshape(batch * frames, channels, height, width) | |
| flat = ( | |
| flat - self.norm_mean.to(dtype=flat.dtype, device=flat.device) | |
| ) / self.norm_std.to(dtype=flat.dtype, device=flat.device) | |
| return flat.reshape(batch, frames, channels, height, width) | |
| def preprocess(self, video_clip_01: torch.Tensor) -> torch.Tensor: | |
| """Official bilinear/ImageNet preprocessing for the sparse API.""" | |
| return self._preprocess_clip(video_clip_01, crop_mode=self.crop_mode) | |
| 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: | |
| """Preprocess exactly one native segment, preserving ``[B,T,C,H,W]``. | |
| Temporal window arguments exist for call-site compatibility with the | |
| V-JEPA2 adapter. VideoMAE V2 segment selection is external, so any | |
| non-``None`` value is rejected. | |
| """ | |
| if temporal_window_starts is not None or temporal_window_indices is not None: | |
| raise ValueError( | |
| "VideoMAE V2 accepts one already-selected 16-frame segment; " | |
| "temporal window selection must be performed by the caller" | |
| ) | |
| return self._preprocess_clip(video_clip_01, crop_mode="center") | |
| # -- OpenGVLab embedding and blocks ----------------------------------- | |
| def _embed_tokens(self, pixel_values: torch.Tensor) -> torch.Tensor: | |
| # OpenGVLab's remote implementation expects BCTHW, unlike the public | |
| # adapter API (and Hugging Face VideoMAE-v1), which uses BTCHW. | |
| bcthw = pixel_values.permute(0, 2, 1, 3, 4).contiguous() | |
| hidden_states = self.backbone.patch_embed(bcthw) | |
| if not isinstance(hidden_states, torch.Tensor) or hidden_states.ndim != 3: | |
| raise RuntimeError( | |
| "OpenGVLab model.patch_embed must return a [B,N,D] tensor" | |
| ) | |
| if int(hidden_states.shape[1]) != self.dense_raw_tokens: | |
| raise RuntimeError( | |
| "unexpected VideoMAE V2 token grid: expected " | |
| f"{self.dense_raw_tokens}, got {hidden_states.shape[1]}" | |
| ) | |
| if int(hidden_states.shape[2]) != self.dense_feature_dim: | |
| raise RuntimeError( | |
| "unexpected VideoMAE V2 feature dimension: expected " | |
| f"{self.dense_feature_dim}, got {hidden_states.shape[2]}" | |
| ) | |
| pos_embed = getattr(self.backbone, "pos_embed") | |
| if pos_embed is not None: | |
| if not isinstance(pos_embed, torch.Tensor): | |
| raise RuntimeError("OpenGVLab model.pos_embed must be a tensor or None") | |
| if pos_embed.ndim == 2: | |
| pos_embed = pos_embed.unsqueeze(0) | |
| if ( | |
| pos_embed.ndim != 3 | |
| or int(pos_embed.shape[1]) != int(hidden_states.shape[1]) | |
| or int(pos_embed.shape[2]) != int(hidden_states.shape[2]) | |
| or int(pos_embed.shape[0]) not in (1, int(hidden_states.shape[0])) | |
| ): | |
| raise RuntimeError( | |
| "OpenGVLab model.pos_embed is incompatible with patch tokens: " | |
| f"{tuple(pos_embed.shape)} vs {tuple(hidden_states.shape)}" | |
| ) | |
| if int(pos_embed.shape[0]) == 1: | |
| pos_embed = pos_embed.expand(int(hidden_states.shape[0]), -1, -1) | |
| # This mirrors the remote forward exactly. In official checkpoints | |
| # sinusoidal position embeddings may be a plain CPU Tensor rather | |
| # than a registered buffer. | |
| hidden_states = hidden_states + ( | |
| pos_embed.type_as(hidden_states) | |
| .to(hidden_states.device) | |
| .clone() | |
| .detach() | |
| ) | |
| hidden_states = self.backbone.pos_drop(hidden_states) | |
| if not isinstance(hidden_states, torch.Tensor): | |
| raise RuntimeError("OpenGVLab model.pos_drop must return a tensor") | |
| self._raw_dim = int(hidden_states.shape[-1]) | |
| return hidden_states | |
| def _run_block(block: nn.Module, hidden_states: torch.Tensor) -> torch.Tensor: | |
| output = block(hidden_states) | |
| if not isinstance(output, torch.Tensor): | |
| raise RuntimeError( | |
| "OpenGVLab VideoMAE V2 blocks must return the post-residual tensor" | |
| ) | |
| return output | |
| def _encode_tokens(self, pixel_values: torch.Tensor) -> torch.Tensor: | |
| """Return final post-block tokens for the historical sparse API.""" | |
| if self._dense_pruned: | |
| raise RuntimeError( | |
| "forward_sparse is unavailable after prune_for_dense_prefix(); " | |
| "construct VideoMAEv2Adapter with dense_only=False to retain it" | |
| ) | |
| self._validate_preprocessed(pixel_values) | |
| hidden_states = self._embed_tokens(pixel_values) | |
| for block in self.backbone.blocks: | |
| hidden_states = self._run_block(block, hidden_states) | |
| # Deliberately before mean pooling and fc_norm. | |
| return hidden_states | |
| # -- intermediate dense feature extraction --------------------------- | |
| 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: | |
| 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]: | |
| 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: | |
| target_layer = ( | |
| self.dense_layer_index if layer_index is None else self._layer_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 _validate_preprocessed(self, pixel_values: torch.Tensor) -> None: | |
| if pixel_values.ndim != 5: | |
| raise ValueError( | |
| "pixel_values must have shape [B,T,3,H,W], " | |
| f"got {tuple(pixel_values.shape)}" | |
| ) | |
| if int(pixel_values.shape[2]) != 3: | |
| raise ValueError( | |
| f"pixel_values must have 3 channels, got {pixel_values.shape[2]}" | |
| ) | |
| if int(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:])}" | |
| ) | |
| def _layer_int(value: object) -> int: | |
| if isinstance(value, bool) or not isinstance(value, Integral): | |
| raise TypeError(f"layer index must be an integer, got {value!r}") | |
| return int(value) | |
| def _token_index_tensor( | |
| values: torch.Tensor | Sequence[int], | |
| *, | |
| device: torch.device, | |
| num_tokens: int, | |
| ) -> torch.Tensor: | |
| if isinstance(values, torch.Tensor): | |
| if ( | |
| values.dtype == torch.bool | |
| or values.is_floating_point() | |
| or values.is_complex() | |
| ): | |
| raise TypeError("token_indices must contain only integers") | |
| indices = values.to(device=device, dtype=torch.long) | |
| else: | |
| if any(isinstance(value, bool) or not isinstance(value, Integral) for value in values): | |
| raise TypeError("token_indices must contain only integers") | |
| indices = torch.tensor(values, device=device, dtype=torch.long) | |
| 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 int(indices.min()) < 0 or int(indices.max()) >= num_tokens: | |
| raise IndexError(f"token_indices must be in [0,{num_tokens - 1}]") | |
| return indices | |
| 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 prefix and capture complete post-residual block outputs.""" | |
| self._validate_preprocessed(pixel_values) | |
| try: | |
| requested_layers = tuple(self._layer_int(layer) for layer in layer_indices) | |
| except TypeError: | |
| raise | |
| 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 layer in requested_layers: | |
| self._validate_dense_layer(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}" | |
| ) | |
| hidden_states = self._embed_tokens(pixel_values) | |
| indices = None | |
| if token_indices is not None: | |
| indices = self._token_index_tensor( | |
| token_indices, | |
| device=hidden_states.device, | |
| num_tokens=int(hidden_states.shape[1]), | |
| ) | |
| requested_set = set(requested_layers) | |
| captured: Dict[int, torch.Tensor] = {} | |
| for block_index in range(max_target_layer + 1): | |
| block = self.backbone.blocks[block_index] | |
| def run_block(x: torch.Tensor, module=block) -> torch.Tensor: | |
| return self._run_block(module, x) | |
| 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: | |
| 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, | |
| ) -> "VideoMAEv2Adapter": | |
| """Irreversibly retain embeddings and blocks through ``layer_index``.""" | |
| target_layer = ( | |
| self.dense_layer_index if layer_index is None else self._layer_int(layer_index) | |
| ) | |
| self._validate_dense_layer(target_layer) | |
| if target_layer >= self.retained_dense_blocks: | |
| raise ValueError( | |
| f"cannot restore pruned layer {target_layer}; only " | |
| f"{self.retained_dense_blocks} blocks remain" | |
| ) | |
| self.backbone.blocks = nn.ModuleList( | |
| list(self.backbone.blocks[: target_layer + 1]) | |
| ) | |
| # Dense prefix exits before all of these official final-path modules. | |
| for name in ("norm", "fc_norm", "head_dropout", "head"): | |
| if hasattr(self.backbone, name): | |
| setattr(self.backbone, name, None) | |
| self._dense_pruned = True | |
| self.dense_only = True | |
| self.dense_layer_index = target_layer | |
| self.freeze() | |
| return self | |
| def _validate_dense_layer(self, layer_index: int) -> None: | |
| layer_index = self._layer_int(layer_index) | |
| if layer_index < 0: | |
| raise ValueError( | |
| f"dense layer index must be non-negative, got {layer_index}" | |
| ) | |
| if self._configured_depth <= 0: | |
| raise RuntimeError("VideoMAE V2 model depth is unavailable") | |
| if layer_index >= self._configured_depth: | |
| raise ValueError( | |
| f"dense layer index {layer_index} out of range for " | |
| f"{self._configured_depth} encoder blocks" | |
| ) | |