Feature Extraction
Transformers
Safetensors
English
lessvit
hyperspectral
remote-sensing
geospatial
vision-transformer
custom_code
Instructions to use GFM-Bench/LESSViT-Base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use GFM-Bench/LESSViT-Base with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="GFM-Bench/LESSViT-Base", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("GFM-Bench/LESSViT-Base", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download attention_ops.py from GFM-Bench/LESSViT-Base: direct link, hf CLI and curl.
- Browser
- Download file 17.7 kB
-
https://huggingface.co/GFM-Bench/LESSViT-Base/resolve/main/attention_ops.py
- Command line
-
hf download hf://GFM-Bench/LESSViT-Base/attention_ops.py
-
curl -L -o attention_ops.py https://huggingface.co/GFM-Bench/LESSViT-Base/resolve/main/attention_ops.py
17.7 kB
| from typing import Optional | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from timm.layers import Mlp, DropPath, use_fused_attn | |
| from .low_rank_attention import AttentionBranch, LowDimPool, LowRankAttention | |
| from .pos_chan_embed import rope_rotate_half | |
| __all__ = [ | |
| 'SpatialSpectralAttention', | |
| 'LESSAttention', | |
| 'FullSpatialSpectralAttention', | |
| 'AdditiveSpatialSpectralAttention', | |
| 'build_attention', | |
| 'LayerScale', | |
| 'LowRankBlock', | |
| ] | |
| class SpatialSpectralAttention(nn.Module): | |
| """ | |
| Common interface for the block-level spatial-spectral attention operator. | |
| forward(x, spatial_mask=None, rope=None) -> Tensor | |
| x: [B, C, HW, D] (C and HW here are whatever the caller's grid axis sizes | |
| currently are, e.g. C+1/HW+1 when CLS tokens are folded in). | |
| spatial_mask: optional perception-field mask, [HW, HW] or None. | |
| rope: optional (sin_hw, cos_hw, sin_c, cos_c) tuple from | |
| RopePositionChannelEmbedding, or None. | |
| returns: [B, C, HW, D], same shape as x. | |
| """ | |
| pass | |
| class LESSAttention(SpatialSpectralAttention): | |
| """ | |
| Arm A: the existing LESS (Kronecker) attention. Thin composition of the | |
| unmodified LowDimPool + LowRankAttention classes behind the unified interface; | |
| no math differs from the original LowRankBlock.forward. | |
| """ | |
| def __init__( | |
| self, | |
| dim: int, | |
| channel_dim: int, | |
| spatial_dim: int, | |
| num_heads: int = 8, | |
| qkv_bias: bool = False, | |
| qk_norm: bool = False, | |
| attn_drop: float = 0., | |
| proj_drop: float = 0., | |
| norm_layer: nn.Module = nn.LayerNorm, | |
| rank: int = 1, | |
| skip_pool: bool = False, | |
| ) -> None: | |
| super().__init__() | |
| self.low_dim_pool = LowDimPool( | |
| dim=dim, | |
| channel_dim=channel_dim, | |
| spatial_dim=spatial_dim, | |
| num_heads=num_heads, | |
| qkv_bias=qkv_bias, | |
| qk_norm=qk_norm, | |
| attn_drop=attn_drop, | |
| proj_drop=proj_drop, | |
| norm_layer=norm_layer, | |
| skip_pool=skip_pool, | |
| ) | |
| self.channel_norm = norm_layer(channel_dim) | |
| self.spatial_norm = norm_layer(spatial_dim) | |
| self.attn = LowRankAttention( | |
| dim=dim, | |
| channel_dim=channel_dim, | |
| spatial_dim=spatial_dim, | |
| num_heads=num_heads, | |
| qkv_bias=qkv_bias, | |
| qk_norm=qk_norm, | |
| attn_drop=attn_drop, | |
| proj_drop=proj_drop, | |
| norm_layer=norm_layer, | |
| rank=rank, | |
| ) | |
| def forward(self, x: torch.Tensor, spatial_mask: torch.Tensor = None, rope: tuple = None) -> torch.Tensor: | |
| x_c, x_s, _ = self.low_dim_pool(x) | |
| return self.attn(self.channel_norm(x_c), self.spatial_norm(x_s), spatial_mask, rope) | |
| class AdditiveSpatialSpectralAttention(SpatialSpectralAttention): | |
| """ | |
| Arm C: *identical* channel/spatial branch attentions as arm A -- same | |
| LowDimPool, same AttentionBranch class, same channel_dim/spatial_dim (so | |
| c_head_dim/s_head_dim keep arm A's low-rank widths, e.g. 4 and 16 for ViT-S, | |
| NOT widened to D_h). This isolates the fusion mechanism specifically, rather | |
| than also widening the branch attentions (an earlier version of this class | |
| set channel_dim=spatial_dim=dim, which inflated LowDimPool's projections and | |
| the branch attentions themselves and produced a ~3x arm A param count -- this | |
| version's param delta over arm A is just P_c/P_s, i.e. genuinely "slight"). | |
| Fusion: each branch's per-head output is projected *up* to the full head_dim | |
| D_h only at the fusion step, then broadcast-added: | |
| Y[c, n] = P_c(Y_C[c]) + P_s(Y_S[n]), P_c: R^c_head_dim -> R^D_h, | |
| P_s: R^s_head_dim -> R^D_h | |
| P_c/P_s are shared across heads (a single small matrix broadcast over the | |
| heads dimension, mirroring how the Kronecker fusion also has no per-head- | |
| distinct fusion parameters). `fusion_init_scale` rescales P_c/P_s's | |
| initialization so that arm C's post-fusion activation std at initialization | |
| matches arm A's (see ablation_sanity_checks.py check #4) -- the two fusion | |
| mechanisms are algebraically different (product vs. sum) so matching | |
| variance requires an explicit scale, not just "sensible" init. | |
| """ | |
| def __init__( | |
| self, | |
| dim: int, | |
| channel_dim: int, | |
| spatial_dim: int, | |
| num_heads: int = 8, | |
| qkv_bias: bool = False, | |
| qk_norm: bool = False, | |
| attn_drop: float = 0., | |
| proj_drop: float = 0., | |
| norm_layer: nn.Module = nn.LayerNorm, | |
| rank: int = 1, | |
| skip_pool: bool = False, | |
| fusion_init_scale: float = 1.0, | |
| ) -> None: | |
| super().__init__() | |
| assert channel_dim % num_heads == 0, 'channel_dim should be divisible by num_heads' | |
| assert spatial_dim % num_heads == 0, 'spatial_dim should be divisible by num_heads' | |
| assert dim % num_heads == 0, 'dim should be divisible by num_heads' | |
| self.num_heads = num_heads | |
| self.dim = dim | |
| self.head_dim = dim // num_heads | |
| self.c_head_dim = channel_dim // num_heads | |
| self.s_head_dim = spatial_dim // num_heads | |
| self.rank = rank | |
| self.low_dim_pool = LowDimPool( | |
| dim=dim, | |
| channel_dim=channel_dim, | |
| spatial_dim=spatial_dim, | |
| num_heads=num_heads, | |
| qkv_bias=qkv_bias, | |
| qk_norm=qk_norm, | |
| attn_drop=attn_drop, | |
| proj_drop=proj_drop, | |
| norm_layer=norm_layer, | |
| skip_pool=skip_pool, | |
| ) | |
| self.channel_norm = norm_layer(channel_dim) | |
| self.spatial_norm = norm_layer(spatial_dim) | |
| self.channel_branch = AttentionBranch(channel_dim, num_heads, self.c_head_dim, qkv_bias, qk_norm, attn_drop, norm_layer, rank) | |
| self.spatial_branch = AttentionBranch(spatial_dim, num_heads, self.s_head_dim, qkv_bias, qk_norm, attn_drop, norm_layer, rank) | |
| self.P_c = nn.Linear(self.c_head_dim, self.head_dim) | |
| self.P_s = nn.Linear(self.s_head_dim, self.head_dim) | |
| with torch.no_grad(): | |
| # Scale the whole affine map (weight AND bias) so that P(x) -> scale*P(x) | |
| # exactly, uniformly rescaling arm C's post-fusion output to match arm A's | |
| # activation std at init (see ablation_sanity_checks.check_fusion_init_scale_calibration). | |
| self.P_c.weight.mul_(fusion_init_scale) | |
| self.P_c.bias.mul_(fusion_init_scale) | |
| self.P_s.weight.mul_(fusion_init_scale) | |
| self.P_s.bias.mul_(fusion_init_scale) | |
| self.proj = nn.Linear(dim, dim) | |
| self.proj_drop = nn.Dropout(proj_drop) | |
| def forward(self, x_grid: torch.Tensor, spatial_mask: torch.Tensor = None, rope: tuple = None) -> torch.Tensor: | |
| x_c, x_s, _ = self.low_dim_pool(x_grid) | |
| C = x_c.shape[1] | |
| HW = x_s.shape[1] | |
| sin_hw, cos_hw, sin_c, cos_c = rope if rope is not None else (None, None, None, None) | |
| xc = self.channel_branch(self.channel_norm(x_c), sin=sin_c, cos=cos_c) # B, rank, num_heads, C, c_head_dim | |
| xs = self.spatial_branch(self.spatial_norm(x_s), spatial_mask, sin=sin_hw, cos=cos_hw) # B, rank, num_heads, HW, s_head_dim | |
| xc = xc.sum(dim=1) / self.rank # B, num_heads, C, c_head_dim | |
| xs = xs.sum(dim=1) / self.rank # B, num_heads, HW, s_head_dim | |
| Yc = self.P_c(xc) # B, num_heads, C, head_dim | |
| Ys = self.P_s(xs) # B, num_heads, HW, head_dim | |
| Y = Yc.unsqueeze(3) + Ys.unsqueeze(2) # B, num_heads, C, HW, head_dim | |
| B = Y.shape[0] | |
| Y = Y.permute(0, 2, 3, 1, 4).reshape(B, C, HW, -1) # B, C, HW, D | |
| Y = self.proj(Y) | |
| Y = self.proj_drop(Y) | |
| return Y | |
| def _apply_axis_rope(x: torch.Tensor, sin: Optional[torch.Tensor], cos: Optional[torch.Tensor], axis: int) -> torch.Tensor: | |
| """ | |
| Rotate a sub-block of the head dimension of `x` ([B, num_heads, C, HW, W]) | |
| along one grid axis (axis=2 for the channel axis, axis=3 for the position | |
| axis), skipping index 0 along that axis (the channel-CLS row when axis=2, the | |
| spatial-CLS column when axis=3). sin/cos: [B, L, W] where L == x.shape[axis]-1. | |
| Reuses the exact rotate-half formula from pos_chan_embed.rope_apply; only the | |
| broadcasting is generalized here to the extra grid axis that arm A's per-branch | |
| tensors never had. | |
| """ | |
| if sin is None: | |
| return x | |
| L = x.shape[axis] - 1 | |
| x_cls, x_rest = x.split([1, L], dim=axis) | |
| view_shape = [x.shape[0]] + [1] * (x.dim() - 2) + [x.shape[-1]] | |
| view_shape[axis] = L | |
| sin = sin.reshape(view_shape) | |
| cos = cos.reshape(view_shape) | |
| x_rot = x_rest * cos + rope_rotate_half(x_rest) * sin | |
| return torch.cat([x_cls, x_rot], dim=axis) | |
| class FullSpatialSpectralAttention(SpatialSpectralAttention): | |
| """ | |
| Arm B: standard full softmax attention over the flattened (channel, position) | |
| token grid (paper Appendix E, Algorithm 2). No low-rank branch factorization, | |
| no Kronecker fusion -- a single Q/K/V, softmax over all C*HW tokens. | |
| Joint RoPE: partitions the head dimension D_h into a spatial (u,v) sub-block | |
| of width `spatial_split_dim` and a spectral (lambda) sub-block of width | |
| `spectral_split_dim` (spatial_split_dim + spectral_split_dim == D_h). Each | |
| sub-block is rotated independently -- spatial sub-block using (sin_hw, cos_hw) | |
| broadcast over the channel axis, spectral sub-block using (sin_c, cos_c) | |
| broadcast over the position axis -- reusing RopePositionChannelEmbedding's | |
| existing frequency construction unmodified (see model config: rope is built | |
| with rope_spatial_split_dim/rope_channel_split_dim instead of the per-branch | |
| spatial_dim/channel_dim that arms A/C use). Rotation is applied while the grid | |
| is still 2D-indexed (before the C/HW flatten), so each axis's CLS-skip is | |
| independent: real tokens (c>=1, n>=1) get both rotations; the channel-CLS row | |
| (c=0) gets spatial rotation only; the spatial-CLS column (n=0) gets spectral | |
| rotation only; the global CLS (0,0) gets neither. | |
| """ | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_heads: int = 8, | |
| qkv_bias: bool = False, | |
| qk_norm: bool = False, | |
| attn_drop: float = 0., | |
| proj_drop: float = 0., | |
| norm_layer: nn.Module = nn.LayerNorm, | |
| ) -> None: | |
| super().__init__() | |
| assert dim % num_heads == 0, 'dim should be divisible by num_heads' | |
| self.num_heads = num_heads | |
| self.head_dim = dim // num_heads | |
| assert self.head_dim % 2 == 0, 'head_dim must be even for RoPE rotate-half' | |
| self.scale = self.head_dim ** -0.5 | |
| self.fused_attn = use_fused_attn() | |
| self.spatial_split_dim = self.head_dim // 2 | |
| self.spectral_split_dim = self.head_dim - self.spatial_split_dim | |
| assert self.spatial_split_dim % 2 == 0 and self.spectral_split_dim % 2 == 0, \ | |
| 'both rope sub-blocks must be even for rotate-half' | |
| self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) | |
| self.q_norm = norm_layer(self.head_dim) if qk_norm else nn.Identity() | |
| self.k_norm = norm_layer(self.head_dim) if qk_norm else nn.Identity() | |
| self.attn_drop = nn.Dropout(attn_drop) | |
| self.proj = nn.Linear(dim, dim) | |
| self.proj_drop = nn.Dropout(proj_drop) | |
| def apply_joint_rope(self, q: torch.Tensor, k: torch.Tensor, rope: tuple): | |
| sin_hw, cos_hw, sin_c, cos_c = rope | |
| def rotate(t: torch.Tensor) -> torch.Tensor: | |
| t_spa, t_spec = t.split([self.spatial_split_dim, self.spectral_split_dim], dim=-1) | |
| t_spa = _apply_axis_rope(t_spa, sin_hw, cos_hw, axis=3) # rotate along the HW (position) axis | |
| t_spec = _apply_axis_rope(t_spec, sin_c, cos_c, axis=2) # rotate along the C (channel) axis | |
| return torch.cat([t_spa, t_spec], dim=-1) | |
| return rotate(q), rotate(k) | |
| def forward(self, x: torch.Tensor, spatial_mask: torch.Tensor = None, rope: tuple = None) -> torch.Tensor: | |
| if spatial_mask is not None: | |
| raise NotImplementedError( | |
| 'perception_field_mask is not supported for attn_type="full" in this ablation ' | |
| '(not exercised by the shared base config -- fail loudly rather than silently ignore it)' | |
| ) | |
| B, C, HW, D = x.shape | |
| qkv = self.qkv(x).reshape(B, C, HW, 3, self.num_heads, self.head_dim).permute(3, 0, 4, 1, 2, 5) | |
| q, k, v = qkv.unbind(0) # B, num_heads, C, HW, head_dim | |
| if rope is not None: | |
| q, k = self.apply_joint_rope(q, k, rope) | |
| q, k = self.q_norm(q), self.k_norm(k) | |
| q = q.reshape(B, self.num_heads, C * HW, self.head_dim) | |
| k = k.reshape(B, self.num_heads, C * HW, self.head_dim) | |
| v = v.reshape(B, self.num_heads, C * HW, self.head_dim) | |
| if self.fused_attn: | |
| out = F.scaled_dot_product_attention( | |
| q, k, v, | |
| dropout_p=self.attn_drop.p if self.training else 0., | |
| ) | |
| else: | |
| attn = (q @ k.transpose(-2, -1)) * self.scale | |
| attn = attn.softmax(dim=-1) | |
| attn = self.attn_drop(attn) | |
| out = attn @ v | |
| out = out.reshape(B, self.num_heads, C, HW, self.head_dim) | |
| out = out.permute(0, 2, 3, 1, 4).reshape(B, C, HW, D) | |
| out = self.proj(out) | |
| out = self.proj_drop(out) | |
| return out | |
| def build_attention( | |
| attn_type: str, | |
| *, | |
| dim: int, | |
| channel_dim: int, | |
| spatial_dim: int, | |
| num_heads: int, | |
| rank: int = 1, | |
| qkv_bias: bool = False, | |
| qk_norm: bool = False, | |
| attn_drop: float = 0., | |
| proj_drop: float = 0., | |
| norm_layer: nn.Module = nn.LayerNorm, | |
| skip_pool: bool = False, | |
| fusion_init_scale: float = 1.0, | |
| ) -> SpatialSpectralAttention: | |
| if attn_type == 'less': | |
| return LESSAttention( | |
| dim=dim, channel_dim=channel_dim, spatial_dim=spatial_dim, num_heads=num_heads, | |
| qkv_bias=qkv_bias, qk_norm=qk_norm, attn_drop=attn_drop, proj_drop=proj_drop, | |
| norm_layer=norm_layer, rank=rank, skip_pool=skip_pool, | |
| ) | |
| elif attn_type == 'additive': | |
| return AdditiveSpatialSpectralAttention( | |
| dim=dim, channel_dim=channel_dim, spatial_dim=spatial_dim, num_heads=num_heads, | |
| qkv_bias=qkv_bias, qk_norm=qk_norm, attn_drop=attn_drop, proj_drop=proj_drop, | |
| norm_layer=norm_layer, rank=rank, skip_pool=skip_pool, fusion_init_scale=fusion_init_scale, | |
| ) | |
| elif attn_type == 'full': | |
| return FullSpatialSpectralAttention( | |
| dim=dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_norm=qk_norm, | |
| attn_drop=attn_drop, proj_drop=proj_drop, norm_layer=norm_layer, | |
| ) | |
| else: | |
| raise ValueError(f'Unknown attn_type: {attn_type!r}, expected one of "less", "full", "additive"') | |
| class LayerScale(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| init_values: float = 1e-5, | |
| inplace: bool = False, | |
| ) -> None: | |
| super().__init__() | |
| self.inplace = inplace | |
| self.gamma = nn.Parameter(init_values * torch.ones(dim)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return x.mul_(self.gamma) if self.inplace else x * self.gamma | |
| class LowRankBlock(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_heads: int, | |
| channel_dim: int, | |
| spatial_dim: int, | |
| mlp_ratio: float = 4., | |
| qkv_bias: bool = False, | |
| qk_norm: bool = False, | |
| proj_drop: float = 0., | |
| attn_drop: float = 0., | |
| drop_path: float = 0., | |
| init_values: Optional[float] = None, | |
| act_layer: nn.Module = nn.GELU, | |
| norm_layer: nn.Module = nn.LayerNorm, | |
| mlp_layer: nn.Module = Mlp, | |
| skip_pool: bool = False, | |
| rank: int = 1, | |
| use_rope_embed: bool = False, | |
| attn_type: str = 'less', | |
| fusion_init_scale: float = 1.0, | |
| ) -> None: | |
| super().__init__() | |
| self.norm1 = norm_layer(dim) | |
| self.attn = build_attention( | |
| attn_type, | |
| dim=dim, | |
| channel_dim=channel_dim, | |
| spatial_dim=spatial_dim, | |
| num_heads=num_heads, | |
| rank=rank, | |
| qkv_bias=qkv_bias, | |
| qk_norm=qk_norm, | |
| attn_drop=attn_drop, | |
| proj_drop=proj_drop, | |
| norm_layer=norm_layer, | |
| skip_pool=skip_pool, | |
| fusion_init_scale=fusion_init_scale, | |
| ) | |
| self.ls1 = LayerScale(dim, init_values=init_values) if init_values else nn.Identity() | |
| self.drop_path1 = DropPath(drop_path) if drop_path > 0. else nn.Identity() | |
| self.norm2 = norm_layer(dim) | |
| self.mlp = mlp_layer( | |
| in_features=dim, | |
| hidden_features=int(dim * mlp_ratio), | |
| act_layer=act_layer, | |
| drop=proj_drop, | |
| ) | |
| self.ls2 = LayerScale(dim, init_values=init_values) if init_values else nn.Identity() | |
| self.drop_path2 = DropPath(drop_path) if drop_path > 0. else nn.Identity() | |
| self.use_rope_embed = use_rope_embed | |
| def forward(self, x: torch.Tensor, spatial_mask: torch.Tensor = None, pos_chan_embedding: torch.Tensor = None) -> torch.Tensor: | |
| rope = pos_chan_embedding if self.use_rope_embed else None | |
| x = x + self.drop_path1(self.ls1(self.attn(self.norm1(x), spatial_mask, rope))) | |
| x = x + self.drop_path2(self.ls2(self.mlp(self.norm2(x)))) | |
| return x | |