| """ |
| V-JEPA2 ViTG Encoder Wrapper for Tactile Image Processing |
| |
| This module provides a frozen ViTG encoder that processes tactile images |
| and produces 1280-dimensional embeddings for the ACT policy. |
| """ |
|
|
| import torch |
| import torch.nn as nn |
| import torchvision.transforms as transforms |
| from typing import Optional |
|
|
| |
| try: |
| |
| from ModelTrain.vjepa2_compat.model_builder import create_vit_giant, create_vit_large, load_vjepa2_weights |
| VJEPA_AVAILABLE = True |
| except ImportError as e: |
| VJEPA_AVAILABLE = False |
| print(f"WARNING: V-JEPA2 models not available: {e}") |
|
|
|
|
| class ViTGEncoder(nn.Module): |
| """ |
| Wrapper for V-JEPA2 ViTG encoder to process tactile images. |
| |
| The encoder is frozen (all parameters have requires_grad=False) and produces |
| 1280-dimensional embeddings from input tactile images. |
| """ |
| |
| def __init__(self, ckpt_path: str, input_size: int = 224): |
| """ |
| Initialize ViTG encoder from checkpoint. |
| |
| Args: |
| ckpt_path: Path to the V-JEPA2 ViTG checkpoint (.pt file) |
| input_size: Expected input image size (default: 224) |
| """ |
| super().__init__() |
| |
| self.input_size = input_size |
| self.embed_dim = 1280 |
| |
| |
| print(f"Loading ViTG checkpoint from: {ckpt_path}") |
| checkpoint = torch.load(ckpt_path, map_location='cpu') |
| |
| |
| |
| if isinstance(checkpoint, dict): |
| if 'model' in checkpoint: |
| model_state = checkpoint['model'] |
| elif 'state_dict' in checkpoint: |
| model_state = checkpoint['state_dict'] |
| elif 'encoder' in checkpoint: |
| |
| model_state = checkpoint['encoder'] |
| else: |
| |
| model_state = checkpoint |
| else: |
| |
| self.encoder = checkpoint |
| model_state = None |
| |
| |
| |
| if model_state is not None: |
| |
| |
| try: |
| |
| self.encoder = self._create_vitg_model() |
| |
| missing_keys, unexpected_keys = self.encoder.load_state_dict(model_state, strict=False) |
| if missing_keys: |
| print(f"Warning: Missing keys in checkpoint: {missing_keys[:5]}...") |
| if unexpected_keys: |
| print(f"Warning: Unexpected keys in checkpoint: {unexpected_keys[:5]}...") |
| except Exception as e: |
| print(f"Error loading state dict: {e}") |
| print("Attempting to use checkpoint directly as model...") |
| self.encoder = checkpoint |
| |
| |
| self._freeze_encoder() |
| |
| |
| self.encoder.eval() |
| |
| |
| |
| self.preprocess = transforms.Compose([ |
| transforms.Resize((self.input_size, self.input_size), antialias=True), |
| transforms.Normalize( |
| mean=[0.485, 0.456, 0.406], |
| std=[0.229, 0.224, 0.225] |
| ) |
| ]) |
| |
| print(f"ViTG encoder loaded successfully. Embedding dim: {self.embed_dim}") |
| |
| def _create_vitg_model(self): |
| """ |
| Create a ViT-G model architecture. |
| This is a placeholder - actual architecture depends on V-JEPA2 implementation. |
| """ |
| |
| |
| |
| raise NotImplementedError( |
| "Please ensure the checkpoint contains the full model, " |
| "or import the V-JEPA2 model architecture explicitly." |
| ) |
| |
| def _freeze_encoder(self): |
| """Freeze all encoder parameters.""" |
| for param in self.encoder.parameters(): |
| param.requires_grad = False |
| print("ViTG encoder frozen (all parameters set to requires_grad=False)") |
| |
| def forward(self, x: torch.Tensor, return_cls_only: bool = True) -> torch.Tensor: |
| """ |
| Forward pass through ViTG encoder. |
| |
| Args: |
| x: Input tactile images, shape (B, C, H, W) |
| return_cls_only: If True, return only CLS token embedding (B, 1280) |
| If False, return all patch embeddings (B, N, 1280) |
| |
| Returns: |
| embeddings: Tensor of shape (B, 1280) if return_cls_only=True, |
| otherwise (B, N, 1280) where N is number of patches |
| """ |
| |
| x = self.preprocess(x) |
| |
| |
| with torch.no_grad(): |
| |
| output = self.encoder(x) |
| |
| |
| if isinstance(output, tuple): |
| |
| features = output[0] |
| elif isinstance(output, dict): |
| |
| if 'cls_token' in output: |
| features = output['cls_token'] |
| elif 'last_hidden_state' in output: |
| features = output['last_hidden_state'] |
| else: |
| |
| features = list(output.values())[0] |
| else: |
| features = output |
| |
| |
| if return_cls_only: |
| if features.dim() == 3: |
| |
| features = features[:, 0, :] |
| elif features.dim() == 2: |
| |
| pass |
| else: |
| raise ValueError(f"Unexpected feature shape: {features.shape}") |
| |
| return features |
| |
| def get_num_params(self): |
| """Return the number of parameters in the encoder.""" |
| return sum(p.numel() for p in self.encoder.parameters()) |
|
|
|
|
| class ViTGEncoderSimple(nn.Module): |
| """ |
| V-JEPA2 ViT encoder wrapper for tactile image processing. |
| Loads V-JEPA2 checkpoint and creates frozen encoder. |
| Supports both ViT-Giant (1408-dim) and ViT-Large (1024-dim). |
| """ |
| |
| def __init__(self, ckpt_path: str, embed_dim: int = None, input_size: int = 224, model_type: str = 'vitg'): |
| super().__init__() |
| |
| self.model_type = model_type |
| self.input_size = input_size |
| |
| |
| if embed_dim is None: |
| if model_type == 'vitg': |
| self.embed_dim = 1408 |
| elif model_type == 'vitl': |
| self.embed_dim = 1024 |
| else: |
| raise ValueError(f"Unknown model_type: {model_type}. Choose 'vitg' or 'vitl'") |
| else: |
| self.embed_dim = embed_dim |
| |
| print(f"Loading ViT-{model_type.upper()} checkpoint from: {ckpt_path}") |
| |
| |
| checkpoint = torch.load(ckpt_path, map_location='cuda') |
| |
| |
| if hasattr(checkpoint, 'eval'): |
| |
| self.encoder = checkpoint |
| print("Loaded full model from checkpoint") |
| elif isinstance(checkpoint, dict): |
| |
| |
| |
| |
| if not VJEPA_AVAILABLE: |
| raise ImportError( |
| "V-JEPA2 model architecture not available.\n" |
| "The local V-JEPA2 model files should be in ModelTrain/vjepa2_compat/\n" |
| "Check that backbones.py and vision_transformer.py exist there." |
| ) |
| |
| |
| |
| print(f"Creating V-JEPA2 ViT-{model_type.upper()} model (img_size={input_size}, tubelet_size=2)") |
| if model_type == 'vitg': |
| self.encoder = create_vit_giant( |
| img_size=input_size, |
| patch_size=16, |
| num_frames=2, |
| tubelet_size=2, |
| ) |
| elif model_type == 'vitl': |
| self.encoder = create_vit_large( |
| img_size=input_size, |
| patch_size=16, |
| num_frames=2, |
| tubelet_size=2, |
| ) |
| else: |
| raise ValueError(f"Unknown model_type: {model_type}. Choose 'vitg' or 'vitl'") |
| |
| |
| use_target = 'target_encoder' in checkpoint |
| self.encoder = load_vjepa2_weights(self.encoder, ckpt_path, use_target_encoder=use_target) |
| else: |
| raise ValueError(f"Unsupported checkpoint format: {type(checkpoint)}") |
| |
| |
| self.encoder.cuda() |
| for param in self.encoder.parameters(): |
| param.requires_grad = False |
| |
| self.encoder.eval() |
| |
| print(f"ViT-{model_type.upper()} encoder loaded and frozen. Embed dim: {self.embed_dim}") |
| |
| def forward(self, x: torch.Tensor, return_all_tokens: bool = False) -> torch.Tensor: |
| """ |
| Forward pass returning CLS token embeddings or all patch tokens. |
| |
| Args: |
| x: Input images (B, C, H, W), assumed to be already resized and normalized |
| return_all_tokens: If True, return all patch tokens (B, num_patches, embed_dim) |
| If False, return only CLS token (B, embed_dim) |
| |
| Returns: |
| CLS embeddings (B, embed_dim) if return_all_tokens=False |
| All patch tokens (B, num_patches, embed_dim) if return_all_tokens=True |
| """ |
| |
| |
| |
| |
| with torch.no_grad(): |
| |
| |
| if x.dim() == 4: |
| x = x.unsqueeze(2) |
| |
| x = x.repeat(1, 1, 2, 1, 1) |
| |
| |
| features = self.encoder(x) |
| |
| |
| |
| if isinstance(features, (tuple, list)): |
| features = features[0] |
| |
| if features.dim() == 3: |
| if return_all_tokens: |
| |
| return features |
| else: |
| |
| features = features[:, 0, :] |
| elif features.dim() == 2: |
| |
| pass |
| |
| return features |
|
|
|
|