# Adapted from: https://github.com/facebookresearch/dinov2/blob/main/dinov2/models/vision_transformer.py # References: # https://github.com/facebookresearch/dino/blob/main/vision_transformer.py # https://github.com/rwightman/pytorch-image-models/tree/master/timm/models/vision_transformer.py import math from functools import partial from typing import Sequence, Tuple, Union, Callable import torch import torch.nn as nn import torch.utils.checkpoint from torch.nn.init import trunc_normal_ from torch.nn.functional import interpolate from hf_src.layers import ( Mlp, PatchEmbed, SwiGLUFFNFused, MemEffAttention, NestedTensorBlock as Block, LayerScale, RMSNorm, ) def named_apply( fn: Callable, module: nn.Module, name="", depth_first=True, include_root=False ) -> nn.Module: if not depth_first and include_root: fn(module=module, name=name) for child_name, child_module in module.named_children(): child_name = ".".join((name, child_name)) if name else child_name named_apply( fn=fn, module=child_module, name=child_name, depth_first=depth_first, include_root=True, ) if depth_first and include_root: fn(module=module, name=name) return module class BlockChunk(nn.ModuleList): def forward(self, x, return_attention=False): # Adaptation for returing attentions for i, b in enumerate(self): if i < len(self) - 1: x = b(x) else: return b(x, return_attention=return_attention) return x class ViTv2(nn.Module): def __init__( self, *, img_size=518, patch_size=16, in_chans=3, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4.0, qkv_bias=True, ffn_bias=True, proj_bias=True, drop_path_rate=0.0, drop_path_uniform=True, init_values=None, # for layerscale: None or 0 => no layerscale embed_layer=PatchEmbed, act_layer=nn.GELU, block_fn=Block, ffn_layer="mlp", block_chunks=0, num_register_tokens=0, interpolate_antialias=False, interpolate_offset=0.1, num_classes=None, **ignored_kwargs, ): """ Args: img_size (int, tuple): input image size patch_size (int, tuple): patch size in_chans (int): number of input channels embed_dim (int): embedding dimension depth (int): depth of transformer num_heads (int): number of attention heads mlp_ratio (int): ratio of mlp hidden dim to embedding dim qkv_bias (bool): enable bias for qkv if True proj_bias (bool): enable bias for proj in attn if True ffn_bias (bool): enable bias for ffn if True drop_path_rate (float): stochastic depth rate drop_path_uniform (bool): apply uniform drop rate across blocks weight_init (str): weight init scheme init_values (float): layer-scale init values embed_layer (nn.Module): patch embedding layer act_layer (nn.Module): MLP activation layer block_fn (nn.Module): transformer block class ffn_layer (str): "mlp", "swiglu", "swiglufused" or "identity" block_chunks: (int) split block sequence into block_chunks units for FSDP wrap num_register_tokens: (int) number of extra cls tokens (so-called "registers") interpolate_antialias: (str) flag to apply anti-aliasing when interpolating positional embeddings interpolate_offset: (float) work-around offset to apply when interpolating positional embeddings """ super().__init__(**ignored_kwargs) norm_layer = partial(nn.LayerNorm, eps=1e-6) self.img_size = img_size self.num_features = self.embed_dim = embed_dim self.num_tokens = 1 self.n_blocks = depth self.num_heads = num_heads self.patch_size = patch_size self.num_register_tokens = num_register_tokens self.interpolate_antialias = interpolate_antialias self.interpolate_offset = interpolate_offset self.patch_embed = embed_layer( img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim, ) num_patches = self.patch_embed.num_patches self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) self.pos_embed = nn.Parameter( torch.zeros(1, num_patches + self.num_tokens, embed_dim) ) assert num_register_tokens >= 0 self.register_tokens = ( nn.Parameter(torch.zeros(1, num_register_tokens, embed_dim)) if num_register_tokens else None ) if drop_path_uniform is True: dpr = [drop_path_rate] * depth else: dpr = [ x.item() for x in torch.linspace(0, drop_path_rate, depth) ] # stochastic depth decay rule if ffn_layer == "mlp": ffn_layer = Mlp elif ffn_layer == "swiglufused" or ffn_layer == "swiglu": ffn_layer = SwiGLUFFNFused elif ffn_layer == "identity": def f(*args, **kwargs): return nn.Identity() ffn_layer = f else: raise NotImplementedError blocks_list = [ block_fn( dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, proj_bias=proj_bias, ffn_bias=ffn_bias, drop_path=dpr[i], norm_layer=norm_layer, act_layer=act_layer, ffn_layer=ffn_layer, init_values=init_values, ) for i in range(depth) ] if block_chunks > 0: self.chunked_blocks = True chunked_blocks = [] chunksize = depth // block_chunks for i in range(0, depth, chunksize): # this is to keep the block index consistent if we chunk the block list chunked_blocks.append( [nn.Identity()] * i + blocks_list[i : i + chunksize] ) self.blocks = nn.ModuleList([BlockChunk(p) for p in chunked_blocks]) else: self.chunked_blocks = False self.blocks = nn.ModuleList(blocks_list) self.mask_token = None self.norm = norm_layer(embed_dim) self.norm_patch = None self.head = ( nn.Identity() if num_classes is None else nn.Linear(embed_dim, num_classes) ) # Initialize the model's weights self.init_weights() def init_weights(self): trunc_normal_(self.pos_embed, std=0.02) nn.init.normal_(self.cls_token, std=1e-6) if self.register_tokens is not None: nn.init.normal_(self.register_tokens, std=1e-6) if self.mask_token is not None: nn.init.zeros_(self.mask_token) named_apply(init_weights_vit, self) def interpolate_pos_encoding(self, x, w, h): previous_dtype = x.dtype npatch = x.shape[1] - 1 N = self.pos_embed.shape[1] - 1 if npatch == N and w == h: return self.pos_embed pos_embed = self.pos_embed.float() class_pos_embed = pos_embed[:, 0] patch_pos_embed = pos_embed[:, 1:] dim = x.shape[-1] w0 = w // self.patch_size h0 = h // self.patch_size # we add a small number to avoid floating point error in the interpolation # see discussion at https://github.com/facebookresearch/dino/issues/8 w0, h0 = w0 + self.interpolate_offset, h0 + self.interpolate_offset sqrt_N = math.sqrt(N) sx, sy = float(w0) / sqrt_N, float(h0) / sqrt_N patch_pos_embed = interpolate( patch_pos_embed.reshape(1, int(sqrt_N), int(sqrt_N), dim).permute( 0, 3, 1, 2 ), scale_factor=(sx, sy), mode="bicubic", # antialias=self.interpolate_antialias, ) assert int(w0) == patch_pos_embed.shape[-2] assert int(h0) == patch_pos_embed.shape[-1] patch_pos_embed = patch_pos_embed.permute(0, 2, 3, 1).view(1, -1, dim) return torch.cat((class_pos_embed.unsqueeze(0), patch_pos_embed), dim=1).to( previous_dtype ) def prepare_tokens_with_masks(self, x, masks=None): B, nc, w, h = x.shape x = self.patch_embed(x) if masks is not None: x = torch.where( masks.unsqueeze(-1), self.mask_token.to(x.dtype).unsqueeze(0), x ) x = torch.cat((self.cls_token.expand(x.shape[0], -1, -1), x), dim=1) x = x + self.interpolate_pos_encoding(x, w, h) if self.register_tokens is not None: x = torch.cat( ( x[:, :1], self.register_tokens.expand(x.shape[0], -1, -1), x[:, 1:], ), dim=1, ) return x def forward_features_list(self, x_list, masks_list): x = [ self.prepare_tokens_with_masks(x, masks) for x, masks in zip(x_list, masks_list) ] for blk in self.blocks: x = blk(x) all_x = x output = [] for x, masks in zip(all_x, masks_list): cls_tokens = self.norm(x[:, : self.num_register_tokens + 1]) if self.norm_patch is None: patch_tokens = self.norm(x[:, self.num_register_tokens + 1 :]) else: patch_tokens = self.norm_patch(x[:, self.num_register_tokens + 1 :]) output.append( { "latent": cls_tokens[:, 0], "patch_latent": patch_tokens, "raw_latent": x[:, 0], } ) return output def forward_features(self, x, masks=None, last_self_attention=False): if isinstance(x, list): return self.forward_features_list(x, masks) x = self.prepare_tokens_with_masks(x, masks) for i, blk in enumerate(self.blocks): if i < len(self.blocks) - 1: x = blk(x) else: x = blk(x, return_attention=last_self_attention) attn = None if last_self_attention: x, attn = x # Attention is selected from the cls token to the patch tokens only # Thus, we ignore the cls from the patch tokens (i.e., start from 1) attn = attn[:, :, 0, self.num_register_tokens + 1 :] cls_tokens = self.norm(x[:, : self.num_register_tokens + 1]) if self.norm_patch is None: patch_tokens = self.norm(x[:, self.num_register_tokens + 1 :]) else: patch_tokens = self.norm_patch(x[:, self.num_register_tokens + 1 :]) return { "latent": cls_tokens[:, 0], "patch_latent": patch_tokens, "raw_latent": x[:, 0], "last_self_attention": attn, "logits": self.head(cls_tokens[:, 0]), } def forward_head(self, x): # Projection with l2-norm bottleneck x = self.projection_head(x) if self.l2_norm: x = nn.functional.normalize(x, dim=1, p=2) return x def _get_intermediate_layers_not_chunked(self, x, n=1): x = self.prepare_tokens_with_masks(x) # If n is an int, take the n last blocks. If it's a list, take them output, total_block_len = [], len(self.blocks) blocks_to_take = ( range(total_block_len - n, total_block_len) if isinstance(n, int) else n ) for i, blk in enumerate(self.blocks): x = blk(x) if i in blocks_to_take: output.append(x) assert len(output) == len( blocks_to_take ), f"only {len(output)} / {len(blocks_to_take)} blocks found" return output def _get_intermediate_layers_chunked(self, x, n=1): x = self.prepare_tokens_with_masks(x) output, i, total_block_len = [], 0, len(self.blocks[-1]) # If n is an int, take the n last blocks. If it's a list, take them blocks_to_take = ( range(total_block_len - n, total_block_len) if isinstance(n, int) else n ) for block_chunk in self.blocks: for blk in block_chunk[i:]: # Passing the nn.Identity() x = blk(x) if i in blocks_to_take: output.append(x) i += 1 assert len(output) == len( blocks_to_take ), f"only {len(output)} / {len(blocks_to_take)} blocks found" return output def get_intermediate_layers( self, x: torch.Tensor, n: Union[int, Sequence] = 1, # Layers or n last layers to take reshape: bool = False, return_class_token: bool = False, norm=True, ) -> Tuple[Union[torch.Tensor, Tuple[torch.Tensor]]]: if self.chunked_blocks: outputs = self._get_intermediate_layers_chunked(x, n) else: outputs = self._get_intermediate_layers_not_chunked(x, n) class_tokens = [ ( out[:, 0] if not norm else self.norm(out[:, : 1 + self.num_register_tokens])[:, 0] ) for out in outputs ] outputs = [ ( out[:, 1 + self.num_register_tokens :] if not norm else ( self.norm(out[:, self.num_register_tokens + 1 :]) if self.norm_patch is None else self.norm_patch(out[:, self.num_register_tokens + 1 :]) ) ) for out in outputs ] if reshape: B, _, w, h = x.shape outputs = [ out.reshape(B, w // self.patch_size, h // self.patch_size, -1) .permute(0, 3, 1, 2) .contiguous() for out in outputs ] if return_class_token: return tuple(zip(outputs, class_tokens)) return tuple(outputs) def forward(self, xs, masks=None, last_self_attention=False, **kwargs): if not (isinstance(xs, list) or isinstance(xs, tuple)): return self.forward_features(xs, masks, last_self_attention) if masks is None: masks = [None] * len(xs) return self.forward_features_list(xs, masks) def forward_backbone(self, x, last_self_attention=False): out_dict = self.forward_features(x, last_self_attention=last_self_attention) cls_token = out_dict["latent"] x = out_dict["patch_latent"] # Combine the cls token and the patch tokens x = torch.cat((cls_token.unsqueeze(1), x), dim=1) if last_self_attention: return x, out_dict["last_self_attention"] return x def get_last_selfattention(self, x, masks=None): """ Adapted from https://gitlab.com/ziegleto-machine-learning/dino/-/tree/main/ """ if isinstance(x, list): raise NotImplementedError("Not implemented for list of inputs") # return self.forward_features_list(x, masks) x = self.prepare_tokens_with_masks(x, masks) # Run through model, at the last block just return the attention. for i, blk in enumerate(self.blocks): if i < len(self.blocks) - 1: x = blk(x) else: _, attn = blk(x, return_attention=True) return attn def init_weights_vit(module: nn.Module, name: str = ""): if isinstance(module, nn.Linear): torch.nn.init.trunc_normal_(module.weight, std=0.02) if module.bias is not None: nn.init.zeros_(module.bias) if hasattr(module, "bias_mask") and module.bias_mask is not None: o = module.out_features module.bias_mask.fill_(1) module.bias_mask[o // 3 : 2 * o // 3].fill_(0) if isinstance(module, nn.LayerNorm): module.reset_parameters() if isinstance(module, LayerScale): module.reset_parameters() if isinstance(module, PatchEmbed): module.reset_parameters() if isinstance(module, RMSNorm): module.reset_parameters()