Instructions to use 1999xia/ViT_Fast with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- timm
How to use 1999xia/ViT_Fast with timm:
import timm model = timm.create_model("hf_hub:1999xia/ViT_Fast", pretrained=True) - Notebooks
- Google Colab
- Kaggle
| """ | |
| Train Router via Attention Distillation from frozen ViT-B/16 teacher. | |
| Approach: | |
| Frozen ViT-B/16 teacher -> extract CLS->patch attention from last block | |
| -> average over attention heads -> target importance scores (B, N) | |
| MLP Router (same architecture as MAEPatchSelectionViT) | |
| -> predict scores from patch embeddings -> MSE loss | |
| The trained router can then be loaded into MAEPatchSelectionViT, | |
| replacing the randomly initialized router. | |
| Usage: | |
| python train_router_distill.py --dataset cifar100 --gpu 4 | |
| python train_router_distill.py --dataset oxford_pets --gpu 4 | |
| python train_router_distill.py --dataset food101 --gpu 4 | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import os | |
| import sys | |
| import argparse | |
| import time | |
| import types | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from datasets import get_cifar100_loader, get_oxford_pets_loader, get_food101_loader, get_dtd_loader, get_flowers102_loader | |
| import timm | |
| DATASETS = { | |
| 'cifar100': (get_cifar100_loader, 100), | |
| 'oxford_pets': (get_oxford_pets_loader, 37), | |
| 'food101': (get_food101_loader, 101), | |
| 'dtd': (get_dtd_loader, 47), | |
| 'flowers102': (get_flowers102_loader, 102), | |
| } | |
| def make_router(embed_dim=768): | |
| """MLP Router with same architecture as MAEPatchSelectionViT.""" | |
| return nn.Sequential( | |
| nn.Linear(embed_dim, embed_dim // 2), | |
| nn.LayerNorm(embed_dim // 2), | |
| nn.GELU(), | |
| nn.Linear(embed_dim // 2, 1), | |
| ) | |
| def prepare_teacher(device): | |
| """Load frozen ViT-B/16 teacher with monkey-patched last attention to capture weights.""" | |
| model = timm.create_model('vit_base_patch16_224.augreg_in21k', pretrained=True) | |
| model = model.to(device) | |
| model.eval() | |
| for p in model.parameters(): | |
| p.requires_grad_(False) | |
| # Monkey-patch the last attention module to store softmax attention weights | |
| last_attn = model.blocks[-1].attn | |
| orig_forward = last_attn.forward | |
| def _forward_with_capture(self, x, **kwargs): | |
| B, N, C = x.shape | |
| head_dim = C // self.num_heads | |
| qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, head_dim).permute(2, 0, 3, 1, 4) | |
| q, k, v = qkv.unbind(0) | |
| # Apply q_norm/k_norm if they exist (not in base ViT-B/16 but in some variants) | |
| q_norm = getattr(self, 'q_norm', None) | |
| k_norm = getattr(self, 'k_norm', None) | |
| if q_norm is not None: | |
| q = q_norm(q) | |
| if k_norm is not None: | |
| k = k_norm(k) | |
| attn = (q @ k.transpose(-2, -1)) * (head_dim ** -0.5) | |
| attn = attn.softmax(dim=-1) | |
| self._last_attn = attn.detach() # (B, H, N, N) | |
| attn = self.attn_drop(attn) | |
| x = (attn @ v).transpose(1, 2).reshape(B, N, C) | |
| x = self.proj(x) | |
| x = self.proj_drop(x) | |
| return x | |
| last_attn.forward = types.MethodType(_forward_with_capture, last_attn) | |
| return model | |
| def get_teacher_attention(teacher, x): | |
| """Forward teacher and extract CLS->patch attention (averaged over heads).""" | |
| _ = teacher(x) | |
| attn = teacher.blocks[-1].attn._last_attn # (B, H, N, N) | |
| cls_attn = attn[:, :, 0, 1:] # (B, H, N_patches) | |
| cls_attn = cls_attn.mean(dim=1) # (B, N_patches), each row sums to 1 | |
| return cls_attn | |
| def evaluate_correlation(teacher, router, loader, device, k_ratio=0.5): | |
| """Measure top-k% overlap between router scores and teacher attention.""" | |
| router.eval() | |
| overlaps = [] | |
| with torch.no_grad(): | |
| for images, _ in loader: | |
| images = images.to(device) | |
| B = images.shape[0] | |
| attn_targets = get_teacher_attention(teacher, images) # (B, N) | |
| N = attn_targets.shape[1] | |
| # Patch embeddings for router | |
| x = teacher.patch_embed(images) | |
| x = x + teacher.pos_embed[:, 1:, :] | |
| scores = router(x).squeeze(-1) # (B, N) | |
| k = max(1, int(N * k_ratio)) | |
| for i in range(B): | |
| target_topk = attn_targets[i].argsort(descending=True)[:k] | |
| pred_topk = scores[i].argsort(descending=True)[:k] | |
| overlap = len(set(target_topk.tolist()) & set(pred_topk.tolist())) | |
| overlaps.append(overlap / k * 100) | |
| avg = sum(overlaps) / len(overlaps) | |
| return avg | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--dataset', type=str, required=True, choices=list(DATASETS.keys())) | |
| parser.add_argument('--gpu', type=int, default=0) | |
| parser.add_argument('--batch_size', type=int, default=64) | |
| parser.add_argument('--lr', type=float, default=1e-4) | |
| parser.add_argument('--epochs', type=int, default=5) | |
| parser.add_argument('--weight_decay', type=float, default=0.05) | |
| args = parser.parse_args() | |
| device = f'cuda:{args.gpu}' if torch.cuda.is_available() else 'cpu' | |
| print(f'Device: {device}', flush=True) | |
| if torch.cuda.is_available(): | |
| print(f'GPU: {torch.cuda.get_device_name(args.gpu)}', flush=True) | |
| # Data | |
| loader_fn, num_classes = DATASETS[args.dataset] | |
| result = loader_fn(batch_size=args.batch_size, data_dir='./data', num_workers=4) | |
| if len(result) == 4: | |
| train_loader, val_loader, test_loader, n_cls = result | |
| else: | |
| train_loader, test_loader, n_cls = result | |
| val_loader = test_loader | |
| print(f'Dataset: {args.dataset}', flush=True) | |
| print(f' Train: {len(train_loader.dataset)}, Classes: {n_cls}', flush=True) | |
| # Teacher | |
| print('Loading frozen ViT-B/16 teacher...', flush=True) | |
| teacher = prepare_teacher(device) | |
| embed_dim = teacher.embed_dim # 768 | |
| N = teacher.patch_embed.num_patches # 196 | |
| # Router (student) | |
| router = make_router(embed_dim).to(device) | |
| n_params = sum(p.numel() for p in router.parameters()) | |
| print(f'Router params: {n_params/1e3:.1f}K', flush=True) | |
| optimizer = torch.optim.AdamW(router.parameters(), lr=args.lr, | |
| weight_decay=args.weight_decay) | |
| scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs) | |
| # Pre-compute target stats on a small sample | |
| print('Computing target attention statistics...', flush=True) | |
| sample_attns = [] | |
| with torch.no_grad(): | |
| for images, _ in test_loader: | |
| images = images.to(device) | |
| attn = get_teacher_attention(teacher, images) | |
| sample_attns.append(attn) | |
| if len(sample_attns) * images.shape[0] > 500: | |
| break | |
| sample_attns = torch.cat(sample_attns, dim=0) | |
| print(f' Teacher CLS attention: mean={sample_attns.mean():.6f}, ' | |
| f'std={sample_attns.std():.6f}, mean*N={sample_attns.mean()*N:.4f}', flush=True) | |
| del sample_attns | |
| print(f'\n=== Attention Distillation: {args.dataset} ({args.epochs} epochs) ===\n', | |
| flush=True) | |
| for epoch in range(args.epochs): | |
| teacher.eval() | |
| router.train() | |
| total_loss = 0 | |
| n_batches = 0 | |
| epoch_start = time.time() | |
| for batch_idx, (images, _) in enumerate(train_loader): | |
| images = images.to(device) | |
| B = images.shape[0] | |
| # Teacher attention: CLS->patch importance | |
| attn_targets = get_teacher_attention(teacher, images) # (B, N) | |
| # Router on patch embeddings (with positional encoding) | |
| x = teacher.patch_embed(images) | |
| x = x + teacher.pos_embed[:, 1:, :] | |
| scores = router(x).squeeze(-1) # (B, N) | |
| # Loss: predict scaled teacher attention | |
| # Scale target so mean ~ 1.0 (teacher attn sums to 1 per sample) | |
| loss = F.mse_loss(scores, attn_targets * N) | |
| optimizer.zero_grad() | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(router.parameters(), 1.0) | |
| optimizer.step() | |
| total_loss += loss.item() | |
| n_batches += 1 | |
| scheduler.step() | |
| avg_loss = total_loss / n_batches | |
| epoch_time = time.time() - epoch_start | |
| # Validate: correlation on test set | |
| val_overlap = evaluate_correlation(teacher, router, test_loader, device) | |
| print(f'Epoch {epoch+1}/{args.epochs} ({epoch_time:.1f}s): ' | |
| f'Loss={avg_loss:.6f}, Top-50% overlap={val_overlap:.2f}%', flush=True) | |
| # Final evaluation | |
| final_overlap = evaluate_correlation(teacher, router, test_loader, device) | |
| print(f'\n=== Final Results ({args.dataset}) ===', flush=True) | |
| print(f' Top-50% overlap with teacher: {final_overlap:.2f}%', flush=True) | |
| print(f' Random baseline: 50.00%', flush=True) | |
| print(f' Router params: {n_params/1e3:.1f}K', flush=True) | |
| # Save router weights | |
| save_dir = f'./checkpoints/router_distill_{args.dataset}' | |
| os.makedirs(save_dir, exist_ok=True) | |
| torch.save({ | |
| 'router_state_dict': router.state_dict(), | |
| 'dataset': args.dataset, | |
| 'val_overlap': final_overlap, | |
| }, f'{save_dir}/router.pth') | |
| print(f'Saved to {save_dir}/router.pth', flush=True) | |
| print('Done!', flush=True) | |
| if __name__ == '__main__': | |
| main() | |