import torch import torch.nn as nn import torch.nn.functional as F # ===================================================== # PATCH EMBEDDING # ===================================================== class PatchEmbedding(nn.Module): def __init__(self, image_size=32, patch_size=4, dim=256): super().__init__() self.patch_size = patch_size self.num_patches = (image_size // patch_size) ** 2 patch_dim = 3 * patch_size * patch_size self.proj = nn.Sequential( nn.LayerNorm(patch_dim), nn.Linear(patch_dim, dim), nn.LayerNorm(dim), ) def forward(self, img): p = self.patch_size x = img.unfold(2, p, p).unfold(3, p, p) x = x.contiguous().permute(0, 2, 3, 1, 4, 5) x = x.reshape(x.size(0), -1, 3 * p * p) return self.proj(x) # ===================================================== # EARLY BLOCK WITH ATTENTION # ===================================================== class EarlyBlockWithAttn(nn.Module): def __init__(self, dim, heads, mlp_dim, num_layers=2): super().__init__() layers = [] for _ in range(num_layers): layers.append( nn.TransformerEncoderLayer( d_model=dim, nhead=heads, dim_feedforward=mlp_dim, dropout=0.1, batch_first=True, norm_first=True, ) ) self.layers = nn.ModuleList(layers) self._last_attn_weights = None self._register_last_layer_hook() def _register_last_layer_hook(self): last_mha = self.layers[-1].self_attn def hook(module, input, output): if isinstance(output, tuple) and len(output) == 2: self._last_attn_weights = output[1] last_mha.register_forward_hook(hook) def forward(self, x): for layer in self.layers[:-1]: x = layer(x) last_layer = self.layers[-1] last_mha = last_layer.self_attn orig_forward = last_mha.forward self._last_attn_weights = None def forward_with_weights(query, key, value, **kwargs): kwargs["need_weights"] = True kwargs["average_attn_weights"] = True return orig_forward(query, key, value, **kwargs) last_mha.forward = forward_with_weights x = last_layer(x) last_mha.forward = orig_forward return x def get_patch_importance(self, num_patches): w = self._last_attn_weights if w is None: return None patch_w = w[:, 1:, 1:] importance = patch_w.sum(dim=1) importance = importance / ( importance.sum(dim=1, keepdim=True) + 1e-8 ) return importance.detach() # ===================================================== # A3C PATCH POLICY # ===================================================== class A3CPatchPolicy(nn.Module): def __init__(self, dim, num_patches): super().__init__() self.num_patches = num_patches self.context_attn = nn.MultiheadAttention( embed_dim=dim, num_heads=4, dropout=0.1, batch_first=True ) self.context_norm = nn.LayerNorm(dim) self.actor = nn.Sequential( nn.Linear(dim + 1, dim // 2), nn.GELU(), nn.Dropout(0.1), nn.Linear(dim // 2, 2), ) self.critic = nn.Sequential( nn.Linear(dim, dim // 2), nn.GELU(), nn.Linear(dim // 2, 1), ) nn.init.zeros_(self.actor[-1].weight) nn.init.zeros_(self.actor[-1].bias) nn.init.zeros_(self.critic[-1].weight) nn.init.zeros_(self.critic[-1].bias) def forward( self, patch_features, patch_importance, is_training=True, policy_active=True ): B, N, D = patch_features.shape ctx = self.context_norm(patch_features) ctx_out, _ = self.context_attn( ctx, ctx, ctx, need_weights=False ) ctx = ctx + ctx_out if patch_importance is not None: imp = patch_importance.detach() else: imp = torch.ones( B, N, device=patch_features.device ) / N importance_feat = imp.unsqueeze(-1) actor_input = torch.cat( [ctx, importance_feat], dim=-1 ) actor_logits = self.actor(actor_input) actor_probs = F.softmax(actor_logits, dim=-1) entropy = -( actor_probs * (actor_probs + 1e-8).log() ).sum(dim=-1) if not policy_active: mask = torch.ones( B, N, device=patch_features.device ) log_probs = F.log_softmax( actor_logits, dim=-1 )[:, :, 1] elif is_training: dist = torch.distributions.Categorical( probs=actor_probs ) action = dist.sample() log_probs = dist.log_prob(action) mask = action.float() else: mask = ( actor_logits[:, :, 1] > actor_logits[:, :, 0] ).float() log_probs = F.log_softmax( actor_logits, dim=-1 )[:, :, 1] state_repr = ctx.mean(dim=1) value = self.critic(state_repr) return ( mask, log_probs, entropy, value, actor_logits, imp ) # ===================================================== # ADAVIT DYNAMIC # ===================================================== class AdaViTDynamic(nn.Module): def __init__( self, image_size=32, patch_size=4, num_classes=10, dim=256, depth=8, heads=8, mlp_dim=512 ): super().__init__() self.num_patches = ( image_size // patch_size ) ** 2 self.dim = dim self.patch_size = patch_size self.patch_embed = PatchEmbedding( image_size, patch_size, dim ) self.pos_embedding = nn.Parameter( torch.randn( 1, self.num_patches + 1, dim ) * 0.02 ) self.cls_token = nn.Parameter( torch.randn(1, 1, dim) * 0.02 ) self.early_block = EarlyBlockWithAttn( dim, heads, mlp_dim, num_layers=2 ) self.policy = A3CPatchPolicy( dim, self.num_patches ) main_layer = nn.TransformerEncoderLayer( d_model=dim, nhead=heads, dim_feedforward=mlp_dim, dropout=0.1, batch_first=True, norm_first=True, ) self.transformer = nn.TransformerEncoder( main_layer, num_layers=max(depth - 2, 1) ) self.head = nn.Sequential( nn.LayerNorm(dim), nn.Dropout(0.1), nn.Linear(dim, num_classes), ) def forward( self, img, is_training=False, policy_active=True ): B = img.shape[0] patches = self.patch_embed(img) cls = self.cls_token.expand(B, -1, -1) x = torch.cat([cls, patches], dim=1) x = x + self.pos_embedding[:, :x.size(1)] x = self.early_block(x) patch_importance = self.early_block.get_patch_importance( self.num_patches ) patch_features = x[:, 1:] cls_out = x[:, 0:1] ( mask, log_probs, entropy, value, actor_logits, imp_used ) = self.policy( patch_features, patch_importance=patch_importance, is_training=is_training, policy_active=policy_active, ) kept_list = [] max_kept = 0 for b in range(B): kept_idx = mask[b].nonzero(as_tuple=True)[0] if len(kept_idx) == 0: kept_idx = torch.arange( min(4, self.num_patches), device=img.device ) kept_list.append( patch_features[b, kept_idx] ) max_kept = max( max_kept, len(kept_idx) ) padded = torch.zeros( B, max_kept, self.dim, device=img.device ) pad_mask = torch.ones( B, max_kept + 1, dtype=torch.bool, device=img.device ) pad_mask[:, 0] = False for b, k in enumerate(kept_list): length = k.size(0) padded[b, :length] = k pad_mask[b, 1:length + 1] = False x_main = torch.cat( [cls_out, padded], dim=1 ) x_main = self.transformer( x_main, src_key_padding_mask=pad_mask ) logits = self.head(x_main[:, 0]) return { "logits": logits, "mask": mask, "log_probs": log_probs, "entropy": entropy, "value": value, "actor_logits": actor_logits, "keep_prob": F.softmax( actor_logits, dim=-1 )[:, :, 1].detach(), "patch_importance": imp_used, }