Image Classification
Transformers
Safetensors
softmasked_selective_vit
vision-transformer
efficient-transformer
selective-attention
knowledge-distillation
computer-vision
custom_code
Instructions to use XAFT/SM-Selective-ViT-Base-224-Distilled with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use XAFT/SM-Selective-ViT-Base-224-Distilled with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-classification", model="XAFT/SM-Selective-ViT-Base-224-Distilled", trust_remote_code=True) pipe("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/hub/parrots.png")# Load model directly from transformers import AutoModelForImageClassification model = AutoModelForImageClassification.from_pretrained("XAFT/SM-Selective-ViT-Base-224-Distilled", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download selective_vit.py from XAFT/SM-Selective-ViT-Base-224-Distilled: direct link, hf CLI and curl.
- Browser
- Download file 15.1 kB
-
https://huggingface.co/XAFT/SM-Selective-ViT-Base-224-Distilled/resolve/ddcb7ea806ccdd94e9715dad1fc503d2c5797f3c/selective_vit.py
- Command line
-
hf download hf://XAFT/SM-Selective-ViT-Base-224-Distilled@ddcb7ea806ccdd94e9715dad1fc503d2c5797f3c/selective_vit.py
-
curl -L -o selective_vit.py https://huggingface.co/XAFT/SM-Selective-ViT-Base-224-Distilled/resolve/ddcb7ea806ccdd94e9715dad1fc503d2c5797f3c/selective_vit.py
15.1 kB
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from torchvision.ops import StochasticDepth | |
| import math | |
| class SoftMaskedMultiheadAttention(nn.Module): | |
| def __init__(self, embed_dim, num_heads, dropout=0.0, bias=True, | |
| add_bias_kv=True, kdim=None, vdim=None, | |
| scale=8., device=None, dtype=None): | |
| super().__init__() | |
| factory_kwargs = {'device': device, 'dtype': dtype} | |
| self.embed_dim = embed_dim | |
| self.kdim = kdim if kdim is not None else embed_dim | |
| self.vdim = vdim if vdim is not None else embed_dim | |
| self.num_heads = num_heads | |
| self.dropout = dropout | |
| self.scale = scale | |
| assert embed_dim % num_heads == 0, "embed_dim must be divisible by num_heads" | |
| self.head_dim = embed_dim // num_heads | |
| self.q_proj = nn.Linear(embed_dim, embed_dim, bias=bias, **factory_kwargs) | |
| self.k_proj = nn.Linear(self.kdim, embed_dim, bias=bias and add_bias_kv, **factory_kwargs) | |
| self.v_proj = nn.Linear(self.vdim, embed_dim, bias=bias and add_bias_kv, **factory_kwargs) | |
| self.dropout_layer = nn.Dropout(dropout) | |
| self.out_proj = nn.Linear(embed_dim, embed_dim) | |
| self._reset_parameters() | |
| def _reset_parameters(self): | |
| nn.init.xavier_uniform_(self.q_proj.weight) | |
| nn.init.xavier_uniform_(self.k_proj.weight) | |
| nn.init.xavier_uniform_(self.v_proj.weight) | |
| if self.q_proj.bias is not None: | |
| nn.init.constant_(self.q_proj.bias, 0.) | |
| if self.k_proj.bias is not None: | |
| nn.init.constant_(self.k_proj.bias, 0.) | |
| if self.v_proj.bias is not None: | |
| nn.init.constant_(self.v_proj.bias, 0.) | |
| nn.init.xavier_uniform_(self.out_proj.weight) | |
| if self.v_proj.bias is not None: | |
| nn.init.constant_(self.out_proj.bias, 0.) | |
| def forward(self, query, key, value, key_padding_mask=None, | |
| need_weights=True, attn_mask=None, average_attn_weights=True): | |
| """ | |
| query, key, value: shape (L, N, E) | |
| where L is the sequence length, N is the batch size, E is the embedding dimension. | |
| """ | |
| batch_size, tgt_len, embed_dim = query.size() | |
| batch_size, src_len, _ = key.size() | |
| q = self.q_proj(query) | |
| k = self.k_proj(key) | |
| v = self.v_proj(value) | |
| # Reshape q, k, v for multihead attention | |
| q = q.view(batch_size, tgt_len, self.num_heads, self.head_dim).transpose(1,2) | |
| k = k.view(batch_size, src_len, self.num_heads, self.head_dim).transpose(1,2) | |
| v = v.view(batch_size, src_len, self.num_heads, self.head_dim).transpose(1,2) | |
| # Compute scaled dot-product attention scores | |
| scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim) | |
| # scores shape: (batch_size, num_heads, tgt_len, src_len) | |
| # Apply the soft [0, 1] mask | |
| if attn_mask is not None: | |
| # Ensure attn_mask values are in (0, 1] to avoid log(0) | |
| # attn_mask shape [b, l] | |
| attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) | |
| if not self.training: | |
| scores = scores.masked_fill((attn_mask == 0.), float('-inf')) | |
| eps = 1e-6 | |
| attn_mask = attn_mask.clip(min=eps).log() | |
| # attn_mask shape [b, 1, 1, l] | |
| scores = scores + self.scale * attn_mask | |
| # Apply key padding mask | |
| if key_padding_mask is not None: | |
| key_padding_mask = key_padding_mask.view(batch_size, 1, 1, src_len) | |
| scores = scores.masked_fill(key_padding_mask, float('-inf')) | |
| # Compute attention weights | |
| attn_weights = F.softmax(scores, dim=-1) | |
| attn_weights = self.dropout_layer(attn_weights) | |
| # Compute attention output | |
| attn_output = torch.matmul(attn_weights, v) | |
| # attn_output shape: (batch_size, num_heads, tgt_len, head_dim) | |
| # Concatenate heads and project | |
| attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, tgt_len, embed_dim) | |
| attn_output = self.out_proj(attn_output) | |
| if need_weights: | |
| # Optionally average attention weights over heads | |
| if average_attn_weights: | |
| attn_weights = attn_weights.mean(dim=1) | |
| else: | |
| attn_weights = attn_weights | |
| else: | |
| attn_weights = None | |
| return attn_output, attn_weights | |
| def get_ffn(input_dim, output_dim, middle_dim, dropout=0.1): | |
| fc1 = nn.Linear(input_dim, middle_dim) | |
| fc2 = nn.Linear(middle_dim, output_dim) | |
| fc3 = nn.Identity() | |
| return nn.Sequential( | |
| fc1, | |
| nn.GELU(), | |
| nn.Dropout(dropout), | |
| fc2, | |
| nn.Dropout(dropout), | |
| fc3 | |
| ) | |
| # Assuming SoftMaskedMultiheadAttention is already defined as provided earlier | |
| class EncoderBlock(nn.Module): | |
| def __init__(self, input_dim, embed_dim, num_heads, mlp_dim, dropout=0.1, drop_path=0.0, patch_drop=0.0, attention_scale=2., mask_threshold=0.05): | |
| super().__init__() | |
| self.mask_threshold = mask_threshold | |
| self.self_attn = SoftMaskedMultiheadAttention( | |
| embed_dim, num_heads, dropout=dropout, scale=attention_scale | |
| ) | |
| if attention_scale > 0: | |
| self.linear_mask = nn.Linear(input_dim, 1) # Linear layer to compute mask scores | |
| self.patch_drop = nn.Dropout(patch_drop) | |
| else: | |
| self.linear_mask = None | |
| if input_dim != embed_dim: | |
| raise ValueError("embed_dim must equal atten_dim but {input_dim}!={embed_dim}") | |
| else: | |
| self.embed = nn.Identity() | |
| self.project = nn.Identity() | |
| self.norm1 = nn.LayerNorm(embed_dim) | |
| self.norm2 = nn.LayerNorm(embed_dim) | |
| # Feed-forward network (MLP) | |
| self.mlp = get_ffn(embed_dim, embed_dim, mlp_dim, dropout=dropout) | |
| self.path_drop = StochasticDepth(drop_path, mode='row') | |
| self.norm3 = nn.LayerNorm(input_dim) | |
| def _reset_parameters(self): | |
| for n, m in self.named_modules(): | |
| if n.startswith('self_attn'): | |
| continue | |
| if isinstance(m, (nn.Linear, GroupedLinear)): | |
| nn.init.trunc_normal_(m.weight.data, std=0.02) | |
| if m.bias is not None: | |
| nn.init.zeros_(m.bias.data) | |
| nn.init.ones_(self.norm1.weight) | |
| nn.init.zeros_(self.norm1.bias) | |
| nn.init.ones_(self.norm2.weight) | |
| nn.init.zeros_(self.norm2.bias) | |
| nn.init.zeros_(self.norm3.weight) | |
| nn.init.zeros_(self.norm3.bias) | |
| def forward_common(self, x, mask): | |
| """ | |
| x: shape (batch_size, seq_len, embed_dim) | |
| """ | |
| # Compute mask scores: (batch_size, seq_len, 1) | |
| x1 = x | |
| x = self.embed(x) | |
| x = self.norm1(x) | |
| # Apply attention mechanism | |
| attn_output, _ = self.self_attn(x, x, x, attn_mask=mask) | |
| # Add & Norm | |
| x = x + self.path_drop(attn_output) | |
| x = self.norm2(x) | |
| # Feed-forward network | |
| mlp_output = self.mlp(x) | |
| # Add & Norm | |
| x = self.path_drop(self.project(x + mlp_output)) | |
| x = self.norm3(x) | |
| if mask is not None: | |
| x = x * mask.unsqueeze(-1) | |
| x = x1 + x | |
| return x | |
| def get_groups(self, mask, full=False): | |
| n_items, index = (mask != 0.0).sum(-1).cpu().sort(descending=True) | |
| n_items, index = n_items.tolist(), index.tolist() | |
| groups = [] | |
| t = 1.0 if full else 1.2 | |
| for ni, ii in zip(n_items, index): | |
| if ni == 0: | |
| break | |
| if len(groups) == 0 or groups[-1][1] / ni > t: | |
| groups.append(([], ni)) | |
| groups[-1][0].append(ii) | |
| return groups | |
| def infer_forward(self, x, mask, full=False): | |
| """ | |
| The “sparse‐inference” path: for each group of batch‐samples that have the same | |
| number n of tokens ≥ mask_threshold, gather only those top‐n tokens (in original order), | |
| run forward_common on the smaller (b’, n, dim) tensor, then scatter the results back. | |
| Fully masked tokens are left untouched. | |
| """ | |
| # Step 1: Threshold the mask without in-place ops | |
| mask_thresholded = mask * (mask >= self.mask_threshold) | |
| # Step 2: Prepare output tensor (copy of x) | |
| x_out = x.clone() | |
| # Step 3: Group samples by number of kept tokens | |
| groups = self.get_groups(mask_thresholded, full) | |
| # Step 4: Process each group | |
| for batch_indices, n_keep in groups: | |
| x_sel = x[batch_indices] # (Bg, seq_len, input_dim) | |
| mask_sel = mask_thresholded[batch_indices] # (Bg, seq_len) | |
| # Top-k selection and sorting | |
| topk_vals, topk_idx_unsorted = torch.topk(mask_sel, k=n_keep, dim=1, sorted=False) | |
| topk_idx_sorted, _ = topk_idx_unsorted.sort(dim=1) | |
| # Gather tokens in sorted order | |
| idx_expanded = topk_idx_sorted.unsqueeze(-1).expand(-1, -1, x_sel.size(-1)) | |
| X_topk = torch.gather(x_sel, dim=1, index=idx_expanded) | |
| mask_topk = torch.gather(mask_sel, dim=1, index=topk_idx_sorted) | |
| # Run forward pass | |
| results = self.forward_common(X_topk, mask_topk) | |
| # Scatter results into a new x_sel tensor | |
| x_sel_updated = x_sel.clone() | |
| x_sel_updated = x_sel_updated.scatter(1, idx_expanded, results) | |
| # Write the updated batch slice into the new output tensor | |
| x_out[batch_indices] = x_sel_updated | |
| return x_out | |
| def forward(self, x, full=False): | |
| if self.linear_mask is not None: | |
| attn_mask = self.patch_drop(self.linear_mask(x).sigmoid().squeeze(-1)) | |
| else: | |
| attn_mask = None | |
| if not self.training and not attn_mask is None and self.mask_threshold >= 0: | |
| x = self.infer_forward(x, attn_mask, full) | |
| else: | |
| x = self.forward_common(x, attn_mask) | |
| return x, attn_mask | |
| class VisionTransformer(nn.Module): | |
| def __init__( | |
| self, | |
| image_size=256, | |
| patch_size=16, | |
| num_classes=1000, | |
| embed_dim=768, | |
| atten_dim=192, | |
| depth=12, | |
| num_heads=3, | |
| mlp_dim=768, | |
| channels=3, | |
| dropout=0.1, | |
| drop_path=0.1, | |
| patch_drop=0.1, | |
| attention_scale=2., | |
| mask_threshold=0.05, | |
| use_distil_token=False | |
| ): | |
| super().__init__() | |
| assert image_size % patch_size == 0, "Image dimensions must be divisible by the patch size." | |
| num_patches = (image_size // patch_size) ** 2 | |
| # Patch embedding layer | |
| self.patch_embed = nn.Conv2d( | |
| in_channels=channels, | |
| out_channels=embed_dim, | |
| kernel_size=patch_size, | |
| stride=patch_size | |
| ) | |
| # Class token | |
| self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) | |
| # Positional embedding | |
| self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1 + (1 if use_distil_token else 0), embed_dim)) | |
| self.dropout = nn.Dropout(dropout) | |
| # Encoder blocks | |
| self.encoder_layers = nn.ModuleList([ | |
| EncoderBlock( | |
| embed_dim, atten_dim, | |
| num_heads, mlp_dim, | |
| dropout, drop_path * i / (depth - 1), | |
| patch_drop=patch_drop, | |
| attention_scale=attention_scale, | |
| mask_threshold=mask_threshold, | |
| ) | |
| for i in range(depth) | |
| ]) | |
| # Classification head | |
| self.post_norm = nn.LayerNorm(embed_dim) | |
| self.head = nn.Linear(embed_dim, num_classes) | |
| if use_distil_token: | |
| self.dis_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) | |
| self.dis_head = nn.Linear(embed_dim, num_classes) | |
| else: | |
| self.dis_token = None | |
| # Initialize weights | |
| self._init_weights() | |
| def _init_weights(self): | |
| for n, m in self.named_modules(): | |
| if n.startswith('encoder_layers'): | |
| continue | |
| if isinstance(m, (nn.Linear, nn.Conv2d)): | |
| nn.init.trunc_normal_(m.weight.data, std=0.02) | |
| if m.bias is not None: | |
| nn.init.zeros_(m.bias.data) | |
| if isinstance(m, nn.LayerNorm): | |
| nn.init.ones_(m.weight.data) | |
| nn.init.zeros_(m.bias.data) | |
| nn.init.trunc_normal_(self.pos_embed, std=0.02) | |
| if self.cls_token is not None: | |
| nn.init.trunc_normal_(self.cls_token, std=0.02) | |
| if self.dis_token is not None: | |
| nn.init.trunc_normal_(self.dis_token, std=0.02) | |
| def forward_features( | |
| self, | |
| pixel_values, | |
| full=False, | |
| output_hidden_states=False, | |
| ): | |
| """ | |
| Args: | |
| pixel_values: (B, C, H, W) | |
| Returns: | |
| last_hidden_state: (B, N, D) | |
| all_hidden_states: tuple or None | |
| masks: Tensor or None | |
| """ | |
| batch_size = pixel_values.size(0) | |
| hidden_states = [] | |
| # Patch embedding | |
| x = self.patch_embed(pixel_values) | |
| x = x.flatten(2).transpose(1, 2) | |
| # Distillation token | |
| if self.dis_token is not None: | |
| dis_tokens = self.dis_token.expand(batch_size, -1, -1) | |
| x = torch.cat((dis_tokens, x), dim=1) | |
| # CLS token | |
| cls_tokens = self.cls_token.expand(batch_size, -1, -1) | |
| x = torch.cat((cls_tokens, x), dim=1) | |
| # Position + dropout | |
| x = x + self.pos_embed | |
| x = self.dropout(x) | |
| masks = [] | |
| for layer in self.encoder_layers: | |
| x, mask = layer(x, full) | |
| if output_hidden_states: | |
| hidden_states.append(x) | |
| if mask is not None: | |
| masks.append(mask) | |
| x = self.post_norm(x) | |
| if output_hidden_states: | |
| hidden_states = tuple(hidden_states) | |
| else: | |
| hidden_states = None | |
| if len(masks) > 0: | |
| masks = tuple(masks) | |
| else: | |
| masks = None | |
| return x, hidden_states, masks | |
| def forward_classifier(self, hidden_states): | |
| """ | |
| Args: | |
| hidden_states: (B, N, D) | |
| Returns: | |
| logits: (B, num_classes) | |
| dis_logits: (B, num_classes) or None | |
| """ | |
| cls_token = hidden_states[:, 0] | |
| logits = self.head(cls_token) | |
| dis_logits = None | |
| if self.dis_token is not None: | |
| dis_cls_token = hidden_states[:, 1] | |
| dis_logits = self.dis_head(dis_cls_token) | |
| # Inference-time averaging (same as original) | |
| if not self.training: | |
| logits = (logits + dis_logits) / 2 | |
| return logits, dis_logits | |
| def forward(self, x, full=False): | |
| last_hidden_states, hidden_states, masks = self.forward_features(x, full) | |
| logits, dis_logits = self.forward_classifier(last_hidden_states) | |
| return logits, dis_logits, masks | |