lukaskuhn commited on
Commit
175eae2
·
verified ·
1 Parent(s): 298eb4d

LeVJEPA-VideoMix-Large: ViT-L video encoder, EMA weights + modeling code

Browse files
Files changed (5) hide show
  1. README.md +107 -0
  2. config.json +27 -0
  3. configuration_levjepa.py +50 -0
  4. model.safetensors +3 -0
  5. modeling_levjepa.py +1104 -0
README.md ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ library_name: transformers
4
+ pipeline_tag: video-feature-extraction
5
+ tags:
6
+ - video
7
+ - self-supervised
8
+ - jepa
9
+ - vision-transformer
10
+ ---
11
+
12
+ # LeVJEPA-VideoMix-Large
13
+
14
+ A ViT-L/16 video encoder trained with LeV-JEPA, a self-supervised objective combining
15
+ a multi-crop prediction loss with SIGReg. No labels are used at any point.
16
+
17
+ - **303.1M parameters**, 224px, patch 16, 16 frames, **tubelet 1** (one token per frame per patch)
18
+ - **RoPE** position encoding, **block-causal** attention
19
+ - Trained on **VideoMix**: 1,806,869 clips from Kinetics-710, Something-Something v2,
20
+ Walking Tours and PE-Video
21
+ - 85 epochs at a flat 4e-4 followed by a 15-epoch cosine decay to 0
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ import torch
27
+ from transformers import AutoModel
28
+
29
+ model = AutoModel.from_pretrained(
30
+ "galilai-group/LeVJEPA-VideoMix-Large", trust_remote_code=True
31
+ ).eval()
32
+
33
+ # (B, C, T, H, W), ImageNet-normalised
34
+ video = torch.randn(1, 3, 16, 224, 224)
35
+
36
+ with torch.no_grad():
37
+ out = model(pixel_values=video)
38
+
39
+ out.last_hidden_state # (1, 3137, 1024) -- CLS + 16*14*14 patch tokens
40
+ out["pooler_output"] # (1, 1024) -- the CLS token
41
+ ```
42
+
43
+ `trust_remote_code=True` is required: this is a custom architecture (RoPE +
44
+ block-causal attention) rather than a stock `transformers` model, so the modeling
45
+ code ships with the weights.
46
+
47
+ ### Preprocessing
48
+
49
+ Normalise with the ImageNet statistics used in training —
50
+ `mean=[0.485, 0.456, 0.406]`, `std=[0.229, 0.224, 0.225]` — and resize/crop to 224.
51
+ Frames are sampled at roughly 7.5 fps in training, so a 16-frame clip covers about
52
+ two seconds.
53
+
54
+ **For a single image**, repeat it along the temporal axis:
55
+
56
+ ```python
57
+ image = torch.randn(1, 3, 224, 224)
58
+ video = image.unsqueeze(2).repeat(1, 1, 16, 1, 1)
59
+ ```
60
+
61
+ That is exactly how the model is evaluated on ImageNet.
62
+
63
+ ## Attention mode
64
+
65
+ The weights were trained with **block-causal** attention: bidirectional within a
66
+ temporal slot, causal across slots, with CLS as a read-only sink that sees the whole
67
+ clip while no patch attends to it. `config.attn_mode` defaults to `"block_causal"`
68
+ for this reason.
69
+
70
+ > Running these weights under full attention will **not** raise an error — it will
71
+ > quietly return worse features. Leave `attn_mode` alone unless you know why you are
72
+ > changing it. An explicit attention mask also disqualifies SDPA's flash kernel, so
73
+ > expect higher memory than a full-attention ViT-L at the same batch size.
74
+
75
+ ## Weights
76
+
77
+ The released tensors are the **EMA** copy of the encoder (`decay=0.9999`,
78
+ `update_every=32`), which is what we evaluate. There is no separate LR-decay artifact
79
+ to apply — the cosine leg is already baked into these weights.
80
+
81
+ ## Training details
82
+
83
+ | | |
84
+ | --- | --- |
85
+ | Objective | multi-crop prediction + SIGReg (weight 0.02) |
86
+ | Crops | 1 global + 10 local |
87
+ | Token drop | 95%, random, applied inside the encoder forward (training only) |
88
+ | Optimizer | AdamW, lr 4e-4 flat then cosine → 0, weight decay 0.04 |
89
+ | Batch | 3072 global |
90
+ | Precision | bf16-mixed |
91
+
92
+ Token dropping is a training-time regulariser and is inert under `eval()`, so the
93
+ released model returns all 3137 tokens.
94
+
95
+ ## Intended use
96
+
97
+ Frozen feature extraction for video and image understanding — attentive or linear
98
+ probing, retrieval, and as a backbone for downstream heads. It is a self-supervised
99
+ encoder with no classification head.
100
+
101
+ ## Limitations
102
+
103
+ Trained at 224px on ~2-second clips, so it has not seen long-horizon temporal
104
+ structure. Walking Tours substitutes for HowTo100M in the mixture, so the training
105
+ distribution is not identical to V-JEPA's VideoMix2M despite the similar scale.
106
+ Evaluation to date is ImageNet and Something-Something v2; behaviour on other domains
107
+ is uncharacterised.
config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "LeVJEPAModel"
4
+ ],
5
+ "model_type": "levjepa",
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_levjepa.LeVJEPAConfig",
8
+ "AutoModel": "modeling_levjepa.LeVJEPAModel"
9
+ },
10
+ "img_size": 224,
11
+ "patch_size": 16,
12
+ "num_frames": 16,
13
+ "tubelet_size": 1,
14
+ "in_chans": 3,
15
+ "embed_dim": 1024,
16
+ "depth": 24,
17
+ "num_heads": 16,
18
+ "mlp_ratio": 4.0,
19
+ "qkv_bias": true,
20
+ "uniform_power": false,
21
+ "use_rope": true,
22
+ "attn_mode": "block_causal",
23
+ "token_drop_rate": 0.0,
24
+ "token_drop_mode": "random",
25
+ "token_drop_k": 2,
26
+ "torch_dtype": "float32"
27
+ }
configuration_levjepa.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Config for LeV-JEPA video encoders."""
2
+
3
+ from transformers import PretrainedConfig
4
+
5
+
6
+ class LeVJEPAConfig(PretrainedConfig):
7
+ model_type = "levjepa"
8
+
9
+ def __init__(
10
+ self,
11
+ img_size=224,
12
+ patch_size=16,
13
+ num_frames=16,
14
+ tubelet_size=1,
15
+ in_chans=3,
16
+ embed_dim=1024,
17
+ depth=24,
18
+ num_heads=16,
19
+ mlp_ratio=4.0,
20
+ qkv_bias=True,
21
+ uniform_power=False,
22
+ use_rope=True,
23
+ # block_causal is NOT a cosmetic default: these weights were trained with
24
+ # it, and running them under full attention silently degrades results
25
+ # instead of raising.
26
+ attn_mode="block_causal",
27
+ # Token dropping is a training-time regulariser and is inert in eval();
28
+ # kept here so a training config round-trips through the class.
29
+ token_drop_rate=0.0,
30
+ token_drop_mode="random",
31
+ token_drop_k=2,
32
+ **kwargs,
33
+ ):
34
+ self.img_size = img_size
35
+ self.patch_size = patch_size
36
+ self.num_frames = num_frames
37
+ self.tubelet_size = tubelet_size
38
+ self.in_chans = in_chans
39
+ self.embed_dim = embed_dim
40
+ self.depth = depth
41
+ self.num_heads = num_heads
42
+ self.mlp_ratio = mlp_ratio
43
+ self.qkv_bias = qkv_bias
44
+ self.uniform_power = uniform_power
45
+ self.use_rope = use_rope
46
+ self.attn_mode = attn_mode
47
+ self.token_drop_rate = token_drop_rate
48
+ self.token_drop_mode = token_drop_mode
49
+ self.token_drop_k = token_drop_k
50
+ super().__init__(**kwargs)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b6ce2813d8eb70f8e9a783d0783f1a5b992c5e01cf6bd788ed0a446c49554d63
3
+ size 1212429888
modeling_levjepa.py ADDED
@@ -0,0 +1,1104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LeV-JEPA video encoder — ViT with RoPE and optional block-causal attention.
2
+
3
+ The transformer below is vendored verbatim from the training codebase so the
4
+ released weights load into exactly the module that produced them. Only the
5
+ training-time loss (SIGReg) and projection head were removed; they are not
6
+ needed to compute features.
7
+ """
8
+
9
+ import math
10
+ from functools import partial
11
+
12
+ import numpy as np
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ from transformers import PreTrainedModel
17
+ from transformers.modeling_outputs import BaseModelOutput
18
+
19
+ from .configuration_levjepa import LeVJEPAConfig
20
+
21
+
22
+ def _no_grad_trunc_normal_(tensor, mean, std, a, b):
23
+ # Cut & paste from PyTorch official master until it's in a few official releases - RW
24
+ # Method based on https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf
25
+ def norm_cdf(x):
26
+ # Computes standard normal cumulative distribution function
27
+ return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0
28
+
29
+ with torch.no_grad():
30
+ # Values are generated by using a truncated uniform distribution and
31
+ # then using the inverse CDF for the normal distribution.
32
+ l = norm_cdf((a - mean) / std)
33
+ u = norm_cdf((b - mean) / std)
34
+
35
+ # Uniformly fill tensor with values from [l, u], then translate to
36
+ # [2l-1, 2u-1].
37
+ tensor.uniform_(2 * l - 1, 2 * u - 1)
38
+
39
+ # Use inverse cdf transform for normal distribution to get truncated
40
+ # standard normal.
41
+ tensor.erfinv_()
42
+
43
+ # Transform to proper mean, std.
44
+ tensor.mul_(std * math.sqrt(2.0))
45
+ tensor.add_(mean)
46
+
47
+ # Clamp to ensure it's in the proper range.
48
+ tensor.clamp_(min=a, max=b)
49
+ return tensor
50
+
51
+
52
+ def trunc_normal_(tensor, mean=0.0, std=1.0, a=-2.0, b=2.0):
53
+ # type: (Tensor, float, float, float, float) -> Tensor
54
+ return _no_grad_trunc_normal_(tensor, mean, std, a, b)
55
+
56
+
57
+ def get_3d_sincos_pos_embed(
58
+ embed_dim,
59
+ grid_size,
60
+ grid_depth,
61
+ cls_token=False,
62
+ uniform_power=False,
63
+ ):
64
+ """
65
+ grid_size: int of the grid height and width
66
+ grid_depth: int of the grid depth
67
+ returns:
68
+ pos_embed: [grid_depth*grid_size*grid_size, embed_dim] (w/o cls_token)
69
+ or [1+grid_depth*grid_size*grid_size, embed_dim] (w/ cls_token)
70
+ """
71
+ grid_d = np.arange(grid_depth, dtype=float)
72
+ grid_h = np.arange(grid_size, dtype=float)
73
+ grid_w = np.arange(grid_size, dtype=float)
74
+ # order of meshgrid is very important for indexing as [d,h,w]
75
+ grid_h, grid_d, grid_w = np.meshgrid(grid_h, grid_d, grid_w)
76
+
77
+ if not uniform_power:
78
+ h_embed_dim = embed_dim // 4
79
+ w_embed_dim = embed_dim // 4
80
+ d_embed_dim = embed_dim // 2
81
+ else:
82
+ h_embed_dim = w_embed_dim = d_embed_dim = int(np.ceil(embed_dim / 6) * 2)
83
+
84
+ emb_h = get_1d_sincos_pos_embed_from_grid(h_embed_dim, grid_h)
85
+ emb_w = get_1d_sincos_pos_embed_from_grid(w_embed_dim, grid_w)
86
+ emb_d = get_1d_sincos_pos_embed_from_grid(d_embed_dim, grid_d)
87
+ pos_embed = np.concatenate([emb_d, emb_h, emb_w], axis=1)
88
+ pos_embed = pos_embed[:, :embed_dim]
89
+ if cls_token:
90
+ pos_embed = np.concatenate([np.zeros([1, embed_dim]), pos_embed], axis=0)
91
+ return pos_embed
92
+
93
+
94
+ def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False):
95
+ """
96
+ grid_size: int of the grid height and width
97
+ returns:
98
+ pos_embed: [grid_size*grid_size, embed_dim] (w/o cls_token)
99
+ or [1+grid_size*grid_size, embed_dim] (w/ cls_token)
100
+ """
101
+ grid_h = np.arange(grid_size, dtype=float)
102
+ grid_w = np.arange(grid_size, dtype=float)
103
+ # order of meshgrid is very important for indexing as [h, w]
104
+ grid_w, grid_h = np.meshgrid(grid_w, grid_h)
105
+
106
+ emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid_h)
107
+ emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid_w)
108
+ pos_embed = np.concatenate([emb_h, emb_w], axis=1)
109
+ if cls_token:
110
+ pos_embed = np.concatenate([np.zeros([1, embed_dim]), pos_embed], axis=0)
111
+ return pos_embed
112
+
113
+
114
+ def get_1d_sincos_pos_embed(embed_dim, grid_size, cls_token=False):
115
+ """
116
+ embed_dim: output dimension for each position
117
+ grid_size: int of the grid length
118
+ returns:
119
+ pos_embed: [grid_size, embed_dim] (w/o cls_token)
120
+ or [1+grid_size, embed_dim] (w/ cls_token)
121
+ """
122
+ grid = np.arange(grid_size, dtype=float)
123
+ pos_embed = get_1d_sincos_pos_embed_from_grid(embed_dim, grid)
124
+ if cls_token:
125
+ pos_embed = np.concatenate([np.zeros([1, embed_dim]), pos_embed], axis=0)
126
+ return pos_embed
127
+
128
+
129
+ def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
130
+ """
131
+ embed_dim: output dimension for each position
132
+ pos: a list of positions to be encoded: size (M,)
133
+ returns: (M, D)
134
+ """
135
+ assert embed_dim % 2 == 0
136
+ omega = np.arange(embed_dim // 2, dtype=float)
137
+ omega /= embed_dim / 2.0
138
+ omega = 1.0 / 10000**omega
139
+
140
+ pos = pos.reshape(-1)
141
+ out = np.einsum("m,d->md", pos, omega)
142
+
143
+ emb_sin = np.sin(out)
144
+ emb_cos = np.cos(out)
145
+
146
+ emb = np.concatenate([emb_sin, emb_cos], axis=1)
147
+ return emb
148
+
149
+
150
+ class PatchEmbed(nn.Module):
151
+ """
152
+ Image to Patch Embedding
153
+ """
154
+
155
+ def __init__(
156
+ self,
157
+ patch_size=16,
158
+ in_chans=3,
159
+ embed_dim=768,
160
+ ):
161
+ super().__init__()
162
+ self.patch_size = patch_size
163
+ self.proj = nn.Conv2d(
164
+ in_chans,
165
+ embed_dim,
166
+ kernel_size=patch_size,
167
+ stride=patch_size,
168
+ )
169
+
170
+ def forward(self, x):
171
+ B, C, H, W = x.shape
172
+ x = self.proj(x).flatten(2).transpose(1, 2)
173
+ return x
174
+
175
+
176
+ class PatchEmbed3D(nn.Module):
177
+ """
178
+ Image to Patch Embedding
179
+ """
180
+
181
+ def __init__(
182
+ self,
183
+ patch_size=16,
184
+ tubelet_size=2,
185
+ in_chans=3,
186
+ embed_dim=768,
187
+ ):
188
+ super().__init__()
189
+ self.patch_size = patch_size
190
+ self.tubelet_size = tubelet_size
191
+
192
+ self.proj = nn.Conv3d(
193
+ in_channels=in_chans,
194
+ out_channels=embed_dim,
195
+ kernel_size=(tubelet_size, patch_size, patch_size),
196
+ stride=(tubelet_size, patch_size, patch_size),
197
+ )
198
+
199
+ def forward(self, x, **kwargs):
200
+ B, C, T, H, W = x.shape
201
+ x = self.proj(x).flatten(2).transpose(1, 2)
202
+ return x
203
+
204
+
205
+ class MLP(nn.Module):
206
+ def __init__(
207
+ self,
208
+ in_features,
209
+ hidden_features=None,
210
+ out_features=None,
211
+ act_layer=nn.GELU,
212
+ drop=0.0,
213
+ ):
214
+ super().__init__()
215
+ out_features = out_features or in_features
216
+ hidden_features = hidden_features or in_features
217
+ self.fc1 = nn.Linear(in_features, hidden_features)
218
+ self.act = act_layer()
219
+ self.fc2 = nn.Linear(hidden_features, out_features)
220
+ self.drop = nn.Dropout(drop)
221
+
222
+ def forward(self, x):
223
+ x = self.fc1(x)
224
+ x = self.act(x)
225
+ x = self.drop(x)
226
+ x = self.fc2(x)
227
+ x = self.drop(x)
228
+ return x
229
+
230
+
231
+ class Attention(nn.Module):
232
+ def __init__(
233
+ self,
234
+ dim,
235
+ num_heads=8,
236
+ qkv_bias=False,
237
+ qk_scale=None,
238
+ attn_drop=0.0,
239
+ proj_drop=0.0,
240
+ use_sdpa=True,
241
+ ):
242
+ super().__init__()
243
+ self.num_heads = num_heads
244
+ head_dim = dim // num_heads
245
+ self.scale = qk_scale or head_dim**-0.5
246
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
247
+ self.attn_drop = nn.Dropout(attn_drop)
248
+ self.proj = nn.Linear(dim, dim)
249
+ self.proj_drop_prob = proj_drop
250
+ self.proj_drop = nn.Dropout(proj_drop)
251
+ self.use_sdpa = use_sdpa
252
+
253
+ def forward(self, x, attn_mask=None):
254
+ B, N, C = x.shape
255
+ qkv = (
256
+ self.qkv(x)
257
+ .reshape(B, N, 3, self.num_heads, C // self.num_heads)
258
+ .permute(2, 0, 3, 1, 4)
259
+ )
260
+ q, k, v = qkv[0], qkv[1], qkv[2]
261
+
262
+ if self.use_sdpa:
263
+ with torch.backends.cuda.sdp_kernel():
264
+ x = F.scaled_dot_product_attention(
265
+ q,
266
+ k,
267
+ v,
268
+ attn_mask=attn_mask,
269
+ dropout_p=self.proj_drop_prob,
270
+ )
271
+ attn = None
272
+ else:
273
+ attn = (q @ k.transpose(-2, -1)) * self.scale
274
+ if attn_mask is not None:
275
+ attn = attn.masked_fill(~attn_mask, float("-inf"))
276
+ attn = attn.softmax(dim=-1)
277
+ attn = self.attn_drop(attn)
278
+ x = attn @ v
279
+ x = x.transpose(1, 2).reshape(B, N, C)
280
+ x = self.proj(x)
281
+ x = self.proj_drop(x)
282
+ return x, attn
283
+
284
+
285
+ def rotate_queries_or_keys(x, pos):
286
+ B, num_heads, N, D = x.size()
287
+ assert D % 2 == 0, "Embedding dimension must be a multiple of 2 for RoPE"
288
+
289
+ omega = torch.arange(D // 2, dtype=x.dtype, device=x.device)
290
+ omega /= D / 2.0
291
+ omega = 1.0 / 10000**omega
292
+ freq = torch.einsum("..., f -> ... f", pos, omega)
293
+
294
+ emb_sin = freq.sin()
295
+ emb_cos = freq.cos()
296
+ # Match V-JEPA2's pretrained-compatible frequency expansion.
297
+ if pos.dim() == 1:
298
+ # Position ids shared across the batch: (N, D/2) -> (1, 1, N, D).
299
+ emb_sin = emb_sin.unsqueeze(0).unsqueeze(0).repeat(1, 1, 1, 2)
300
+ emb_cos = emb_cos.unsqueeze(0).unsqueeze(0).repeat(1, 1, 1, 2)
301
+ else:
302
+ # Per-sample position ids: (B, N, D/2) -> (B, 1, N, D).
303
+ emb_sin = emb_sin.unsqueeze(1).repeat(1, 1, 1, 2)
304
+ emb_cos = emb_cos.unsqueeze(1).repeat(1, 1, 1, 2)
305
+
306
+ y = x.unflatten(-1, (-1, 2))
307
+ y1, y2 = y.unbind(dim=-1)
308
+ y = torch.stack((-y2, y1), dim=-1)
309
+ y = y.flatten(-2)
310
+ return (x * emb_cos) + (y * emb_sin)
311
+
312
+ # Yoinked from https://github.com/facebookresearch/vjepa2/blob/main/src/models/utils/modules.py
313
+ class RoPEAttention(nn.Module):
314
+ def __init__(
315
+ self,
316
+ dim,
317
+ num_heads=8,
318
+ qkv_bias=False,
319
+ qk_scale=None,
320
+ attn_drop=0.0,
321
+ proj_drop=0.0,
322
+ use_sdpa=True,
323
+ grid_size=14,
324
+ has_cls_token=True,
325
+ ):
326
+ super().__init__()
327
+ self.num_heads = num_heads
328
+ self.head_dim = head_dim = dim // num_heads
329
+ self.scale = qk_scale or head_dim**-0.5
330
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
331
+ self.attn_drop = nn.Dropout(attn_drop)
332
+ self.proj = nn.Linear(dim, dim)
333
+ self.proj_drop_prob = proj_drop
334
+ self.proj_drop = nn.Dropout(proj_drop)
335
+ self.use_sdpa = use_sdpa
336
+ self.grid_size = grid_size
337
+ self.has_cls_token = has_cls_token
338
+
339
+ self.d_dim = int(2 * ((head_dim // 3) // 2))
340
+ self.h_dim = int(2 * ((head_dim // 3) // 2))
341
+ self.w_dim = int(2 * ((head_dim // 3) // 2))
342
+
343
+ def _get_frame_pos(self, ids, H_patches=None, W_patches=None):
344
+ if H_patches is None or W_patches is None:
345
+ tokens_per_frame = int(self.grid_size * self.grid_size)
346
+ else:
347
+ tokens_per_frame = int(H_patches * W_patches)
348
+ return ids // tokens_per_frame
349
+
350
+ def _get_height_pos(self, ids, H_patches=None, W_patches=None):
351
+ if H_patches is None or W_patches is None:
352
+ tokens_per_frame = int(self.grid_size * self.grid_size)
353
+ tokens_per_row = self.grid_size
354
+ else:
355
+ tokens_per_frame = int(H_patches * W_patches)
356
+ tokens_per_row = W_patches
357
+ frame_ids = self._get_frame_pos(ids, H_patches, W_patches)
358
+ ids = ids - tokens_per_frame * frame_ids
359
+ return ids // tokens_per_row
360
+
361
+ def separate_positions(self, ids, H_patches=None, W_patches=None):
362
+ if H_patches is None or W_patches is None:
363
+ tokens_per_frame = int(self.grid_size * self.grid_size)
364
+ tokens_per_row = self.grid_size
365
+ else:
366
+ tokens_per_frame = int(H_patches * W_patches)
367
+ tokens_per_row = W_patches
368
+ frame_ids = self._get_frame_pos(ids, H_patches, W_patches)
369
+ height_ids = self._get_height_pos(ids, H_patches, W_patches)
370
+ width_ids = (ids - tokens_per_frame * frame_ids) - tokens_per_row * height_ids
371
+ return frame_ids, height_ids, width_ids
372
+
373
+ def _apply_rope(self, q, k, pos):
374
+ d_pos, h_pos, w_pos = pos
375
+ s = 0
376
+ qd = rotate_queries_or_keys(q[..., s : s + self.d_dim], pos=d_pos)
377
+ kd = rotate_queries_or_keys(k[..., s : s + self.d_dim], pos=d_pos)
378
+ s += self.d_dim
379
+
380
+ qh = rotate_queries_or_keys(q[..., s : s + self.h_dim], pos=h_pos)
381
+ kh = rotate_queries_or_keys(k[..., s : s + self.h_dim], pos=h_pos)
382
+ s += self.h_dim
383
+
384
+ qw = rotate_queries_or_keys(q[..., s : s + self.w_dim], pos=w_pos)
385
+ kw = rotate_queries_or_keys(k[..., s : s + self.w_dim], pos=w_pos)
386
+ s += self.w_dim
387
+
388
+ if s < self.head_dim:
389
+ q = torch.cat([qd, qh, qw, q[..., s:]], dim=-1)
390
+ k = torch.cat([kd, kh, kw, k[..., s:]], dim=-1)
391
+ else:
392
+ q = torch.cat([qd, qh, qw], dim=-1)
393
+ k = torch.cat([kd, kh, kw], dim=-1)
394
+ return q, k
395
+
396
+ def forward(
397
+ self,
398
+ x,
399
+ T=None,
400
+ H_patches=None,
401
+ W_patches=None,
402
+ token_ids=None,
403
+ attn_mask=None,
404
+ ):
405
+ B, N, C = x.shape
406
+ qkv = (
407
+ self.qkv(x)
408
+ .reshape(B, N, 3, self.num_heads, C // self.num_heads)
409
+ .permute(2, 0, 3, 1, 4)
410
+ )
411
+ q, k, v = qkv[0], qkv[1], qkv[2]
412
+
413
+ cls_tokens = 1 if self.has_cls_token else 0
414
+ patch_N = N - cls_tokens
415
+ if T is None or H_patches is None or W_patches is None:
416
+ T = int(patch_N // (self.grid_size * self.grid_size))
417
+ H_patches = W_patches = self.grid_size
418
+
419
+ if token_ids is not None:
420
+ mask = token_ids
421
+ else:
422
+ mask = torch.arange(int(T * H_patches * W_patches), device=x.device)
423
+ d_mask, h_mask, w_mask = self.separate_positions(mask, H_patches, W_patches)
424
+
425
+ if cls_tokens:
426
+ q_patch, k_patch = self._apply_rope(
427
+ q[..., cls_tokens:, :],
428
+ k[..., cls_tokens:, :],
429
+ (d_mask, h_mask, w_mask),
430
+ )
431
+ q = torch.cat([q[..., :cls_tokens, :], q_patch], dim=-2)
432
+ k = torch.cat([k[..., :cls_tokens, :], k_patch], dim=-2)
433
+ else:
434
+ q, k = self._apply_rope(q, k, (d_mask, h_mask, w_mask))
435
+
436
+ if self.use_sdpa:
437
+ with torch.backends.cuda.sdp_kernel():
438
+ x = F.scaled_dot_product_attention(
439
+ q,
440
+ k,
441
+ v,
442
+ attn_mask=attn_mask,
443
+ dropout_p=self.proj_drop_prob,
444
+ )
445
+ attn = None
446
+ else:
447
+ attn = (q @ k.transpose(-2, -1)) * self.scale
448
+ if attn_mask is not None:
449
+ attn = attn.masked_fill(~attn_mask, float("-inf"))
450
+ attn = attn.softmax(dim=-1)
451
+ attn = self.attn_drop(attn)
452
+ x = attn @ v
453
+
454
+ x = x.transpose(1, 2).reshape(B, N, C)
455
+ x = self.proj(x)
456
+ x = self.proj_drop(x)
457
+ return x, attn
458
+
459
+
460
+ def build_block_causal_mask(
461
+ T,
462
+ H_patches,
463
+ W_patches,
464
+ token_ids=None,
465
+ num_prefix_tokens=1,
466
+ device=None,
467
+ ):
468
+ """Bidirectional inside a temporal slot, causal across slots.
469
+
470
+ Patch tokens are grouped into blocks by their temporal slot (a tubelet of
471
+ `tubelet_size` frames) and a query may attend to every key in its own block
472
+ and in all earlier ones, so spatial context is unrestricted while nothing
473
+ ever reads from the future. Token positions come from `token_ids` -- the
474
+ keep-set token dropping produced -- so the mask describes the true grid
475
+ positions of the surviving tokens rather than their order in the sequence.
476
+
477
+ The CLS prefix is the readout register: its row is all-True so it sees the
478
+ whole clip, but its column is False for patches. Letting patches attend to
479
+ it would route layer-l information about the last frame into a first-frame
480
+ token at layer l+1, which is exactly the leak the mask exists to prevent.
481
+
482
+ Returns a bool mask of shape (B or 1, 1, N, N), True meaning "attend".
483
+ """
484
+ tokens_per_frame = int(H_patches * W_patches)
485
+ if token_ids is None:
486
+ ids = torch.arange(int(T * tokens_per_frame), device=device).unsqueeze(0)
487
+ else:
488
+ ids = token_ids
489
+ frame_ids = ids // tokens_per_frame
490
+ mask = frame_ids.unsqueeze(-1) >= frame_ids.unsqueeze(-2)
491
+ if num_prefix_tokens:
492
+ B, N_patches, _ = mask.shape
493
+ p = int(num_prefix_tokens)
494
+ full = mask.new_zeros((B, N_patches + p, N_patches + p))
495
+ full[:, :p, :] = True
496
+ full[:, p:, p:] = mask
497
+ mask = full
498
+ return mask.unsqueeze(1)
499
+
500
+
501
+ class Block(nn.Module):
502
+ def __init__(
503
+ self,
504
+ dim,
505
+ num_heads,
506
+ mlp_ratio=4.0,
507
+ qkv_bias=False,
508
+ qk_scale=None,
509
+ drop=0.0,
510
+ attn_drop=0.0,
511
+ act_layer=nn.GELU,
512
+ norm_layer=nn.LayerNorm,
513
+ grid_size=None,
514
+ grid_depth=None,
515
+ use_rope=False,
516
+ ):
517
+ super().__init__()
518
+ self.norm1 = norm_layer(dim)
519
+ if use_rope:
520
+ self.attn = RoPEAttention(
521
+ dim,
522
+ num_heads=num_heads,
523
+ qkv_bias=qkv_bias,
524
+ qk_scale=qk_scale,
525
+ attn_drop=attn_drop,
526
+ proj_drop=drop,
527
+ grid_size=grid_size,
528
+ )
529
+ else:
530
+ self.attn = Attention(
531
+ dim,
532
+ num_heads=num_heads,
533
+ qkv_bias=qkv_bias,
534
+ qk_scale=qk_scale,
535
+ attn_drop=attn_drop,
536
+ proj_drop=drop,
537
+ )
538
+
539
+ self.norm2 = norm_layer(dim)
540
+ mlp_hidden_dim = int(dim * mlp_ratio)
541
+ self.mlp = MLP(
542
+ in_features=dim,
543
+ hidden_features=mlp_hidden_dim,
544
+ act_layer=act_layer,
545
+ drop=drop,
546
+ )
547
+
548
+ def forward(
549
+ self,
550
+ x,
551
+ return_attention=False,
552
+ T=None,
553
+ H_patches=None,
554
+ W_patches=None,
555
+ token_ids=None,
556
+ attn_mask=None,
557
+ ):
558
+ if isinstance(self.attn, RoPEAttention):
559
+ y, attn = self.attn(
560
+ self.norm1(x),
561
+ T=T,
562
+ H_patches=H_patches,
563
+ W_patches=W_patches,
564
+ token_ids=token_ids,
565
+ attn_mask=attn_mask,
566
+ )
567
+ else:
568
+ y, attn = self.attn(self.norm1(x), attn_mask=attn_mask)
569
+ if return_attention:
570
+ return attn
571
+ x = x + y
572
+ x = x + self.mlp(self.norm2(x))
573
+ return x
574
+
575
+
576
+ class VisionTransformer(nn.Module):
577
+ """Vision Transformer"""
578
+
579
+ def __init__(
580
+ self,
581
+ img_size=224,
582
+ patch_size=16,
583
+ num_frames=1,
584
+ tubelet_size=2,
585
+ in_chans=3,
586
+ embed_dim=768,
587
+ depth=12,
588
+ num_heads=12,
589
+ mlp_ratio=4.0,
590
+ qkv_bias=True,
591
+ qk_scale=None,
592
+ drop_rate=0.0,
593
+ attn_drop_rate=0.0,
594
+ norm_layer=nn.LayerNorm,
595
+ init_std=0.02,
596
+ out_layers=None,
597
+ uniform_power=False,
598
+ use_rope=False,
599
+ token_drop_rate=0.0,
600
+ token_drop_mode="random",
601
+ token_drop_k=2,
602
+ attn_mode="full",
603
+ **kwargs,
604
+ ):
605
+ super().__init__()
606
+ self.num_features = self.embed_dim = embed_dim
607
+ self.num_heads = num_heads
608
+ self.out_layers = out_layers
609
+ self.token_drop_rate = token_drop_rate
610
+ self.token_drop_mode = token_drop_mode
611
+ self.token_drop_k = int(token_drop_k)
612
+ if attn_mode not in ("full", "block_causal"):
613
+ raise ValueError(f"Unknown attn_mode {attn_mode!r}")
614
+ self.attn_mode = attn_mode
615
+
616
+ self.input_size = img_size
617
+ self.patch_size = patch_size
618
+
619
+ self.num_frames = num_frames
620
+ self.tubelet_size = tubelet_size
621
+ self.is_video = num_frames > 1
622
+
623
+ grid_size = self.input_size // self.patch_size
624
+ grid_depth = self.num_frames // self.tubelet_size
625
+
626
+ # Tokenize pixels with convolution
627
+ if self.is_video:
628
+ self.patch_embed = PatchEmbed3D(
629
+ patch_size=patch_size,
630
+ tubelet_size=tubelet_size,
631
+ in_chans=in_chans,
632
+ embed_dim=embed_dim,
633
+ )
634
+ self.num_patches = (
635
+ (num_frames // tubelet_size)
636
+ * (img_size // patch_size)
637
+ * (img_size // patch_size)
638
+ )
639
+ else:
640
+ self.patch_embed = PatchEmbed(
641
+ patch_size=patch_size,
642
+ in_chans=in_chans,
643
+ embed_dim=embed_dim,
644
+ )
645
+ self.num_patches = (img_size // patch_size) * (img_size // patch_size)
646
+
647
+ # Position embedding
648
+ self.uniform_power = uniform_power
649
+ self.use_rope = use_rope
650
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
651
+ if self.use_rope:
652
+ self.pos_embed = None
653
+ else:
654
+ self.pos_embed = nn.Parameter(
655
+ torch.zeros(1, self.num_patches + 1, embed_dim),
656
+ requires_grad=False,
657
+ )
658
+
659
+ # Attention Blocks
660
+ self.blocks = nn.ModuleList(
661
+ [
662
+ Block(
663
+ dim=embed_dim,
664
+ num_heads=num_heads,
665
+ mlp_ratio=mlp_ratio,
666
+ qkv_bias=qkv_bias,
667
+ qk_scale=qk_scale,
668
+ drop=drop_rate,
669
+ act_layer=nn.GELU,
670
+ grid_size=grid_size,
671
+ grid_depth=grid_depth,
672
+ attn_drop=attn_drop_rate,
673
+ norm_layer=norm_layer,
674
+ use_rope=use_rope,
675
+ )
676
+ for i in range(depth)
677
+ ]
678
+ )
679
+ self.norm = norm_layer(embed_dim)
680
+
681
+ # ------ initialize weights
682
+ if self.pos_embed is not None:
683
+ self._init_pos_embed(self.pos_embed.data) # sincos pos-embed
684
+ self.init_std = init_std
685
+ self.apply(self._init_weights)
686
+ trunc_normal_(self.cls_token, std=self.init_std)
687
+ self._rescale_blocks()
688
+
689
+ def _init_pos_embed(self, pos_embed):
690
+ embed_dim = pos_embed.size(-1)
691
+ grid_size = self.input_size // self.patch_size
692
+ if self.is_video:
693
+ grid_depth = self.num_frames // self.tubelet_size
694
+ sincos = get_3d_sincos_pos_embed(
695
+ embed_dim,
696
+ grid_size,
697
+ grid_depth,
698
+ cls_token=True,
699
+ uniform_power=self.uniform_power,
700
+ )
701
+ else:
702
+ sincos = get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=True)
703
+ pos_embed.copy_(torch.from_numpy(sincos).float().unsqueeze(0))
704
+
705
+ def _init_weights(self, m):
706
+ if isinstance(m, nn.Linear):
707
+ trunc_normal_(m.weight, std=self.init_std)
708
+ if isinstance(m, nn.Linear) and m.bias is not None:
709
+ nn.init.constant_(m.bias, 0)
710
+ elif isinstance(m, nn.LayerNorm):
711
+ nn.init.constant_(m.bias, 0)
712
+ nn.init.constant_(m.weight, 1.0)
713
+ elif isinstance(m, nn.Conv2d):
714
+ trunc_normal_(m.weight, std=self.init_std)
715
+ if m.bias is not None:
716
+ nn.init.constant_(m.bias, 0)
717
+ elif isinstance(m, nn.Conv3d):
718
+ trunc_normal_(m.weight, std=self.init_std)
719
+ if m.bias is not None:
720
+ nn.init.constant_(m.bias, 0)
721
+
722
+ def _rescale_blocks(self):
723
+ def rescale(param, layer_id):
724
+ param.div_(math.sqrt(2.0 * layer_id))
725
+
726
+ for layer_id, layer in enumerate(self.blocks):
727
+ rescale(layer.attn.proj.weight.data, layer_id + 1)
728
+ rescale(layer.mlp.fc2.weight.data, layer_id + 1)
729
+
730
+ def get_num_layers(self):
731
+ return len(self.blocks)
732
+
733
+ def no_weight_decay(self):
734
+ return {}
735
+
736
+ def forward(self, x):
737
+ """
738
+ :param x: input image/video
739
+ """
740
+
741
+ # Tokenize input
742
+ if x.ndim == 4:
743
+ _, _, H, W = x.shape
744
+ T = 1
745
+ elif x.ndim == 5:
746
+ _, _, T, H, W = x.shape
747
+ T = T // self.tubelet_size
748
+ else:
749
+ raise ValueError(f"Expected image or video tensor, got {x.ndim} dimensions")
750
+ H_patches = H // self.patch_size
751
+ W_patches = W // self.patch_size
752
+
753
+ pos_embed = self.pos_embed
754
+ if pos_embed is not None:
755
+ pos_embed = self.interpolate_pos_encoding(x, pos_embed)
756
+ x = self.patch_embed(x)
757
+ if pos_embed is not None:
758
+ cls_pos_embed = pos_embed[:, :1]
759
+ patch_pos_embed = pos_embed[:, 1:]
760
+ x += patch_pos_embed
761
+
762
+ token_ids = None
763
+ if self.training and self.token_drop_rate > 0:
764
+ B, N_patches, C = x.shape
765
+ if self.token_drop_mode == "tube":
766
+ # Keep the same random spatial locations in every temporal slot.
767
+ HW = H_patches * W_patches
768
+ keep_s = max(1, int(round(HW * (1 - self.token_drop_rate))))
769
+ noise = torch.rand(B, HW, device=x.device)
770
+ spatial_ids = noise.argsort(dim=1)[:, :keep_s]
771
+ temporal_offsets = torch.arange(T, device=x.device) * HW
772
+ token_ids = (
773
+ spatial_ids[:, None, :] + temporal_offsets[None, :, None]
774
+ ).reshape(B, T * keep_s)
775
+ token_ids, _ = token_ids.sort(dim=1)
776
+ elif self.token_drop_mode == "tube_k":
777
+ # Keep short tubes: a spatial location survives for k consecutive
778
+ # temporal slots. The temporal axis is cut into T//k aligned blocks
779
+ # of length k, and we sample whole (block, location) pairs, so every
780
+ # kept run is contiguous and runs never overlap. k=1 degenerates to
781
+ # `random`, k=T to `tube`.
782
+ HW = H_patches * W_patches
783
+ k = max(1, min(self.token_drop_k, T))
784
+ if T % k != 0:
785
+ raise ValueError(
786
+ f"token_drop_mode='tube_k' needs k to divide the temporal "
787
+ f"grid, got k={k} and T={T} slots"
788
+ )
789
+ n_blocks = T // k
790
+ keep_len = max(1, int(round(N_patches * (1 - self.token_drop_rate))))
791
+ # Same token budget as the other modes (hence same FLOPs), up to the
792
+ # <k/2 tokens lost to rounding keep_len onto a multiple of k.
793
+ n_seg = min(max(1, int(round(keep_len / k))), HW * n_blocks)
794
+ noise = torch.rand(B, HW * n_blocks, device=x.device)
795
+ seg_ids = noise.argsort(dim=1)[:, :n_seg]
796
+ block_ids, spatial_ids = seg_ids // HW, seg_ids % HW
797
+ offsets = torch.arange(k, device=x.device)
798
+ token_ids = (
799
+ (block_ids[:, :, None] * k + offsets[None, None, :]) * HW
800
+ + spatial_ids[:, :, None]
801
+ ).reshape(B, n_seg * k)
802
+ token_ids, _ = token_ids.sort(dim=1)
803
+ else:
804
+ # Draw an independent keep-set over the full temporal-spatial grid.
805
+ keep_len = max(
806
+ 1, int(round(N_patches * (1 - self.token_drop_rate)))
807
+ )
808
+ noise = torch.rand(B, N_patches, device=x.device)
809
+ token_ids = noise.argsort(dim=1)[:, :keep_len]
810
+
811
+ x = torch.gather(
812
+ x,
813
+ dim=1,
814
+ index=token_ids.unsqueeze(-1).expand(-1, -1, C),
815
+ )
816
+
817
+ cls_token = self.cls_token.expand(x.shape[0], -1, -1)
818
+ if pos_embed is not None:
819
+ cls_token = cls_token + cls_pos_embed
820
+ x = torch.cat((cls_token, x), dim=1)
821
+
822
+ attn_mask = None
823
+ if self.attn_mode == "block_causal":
824
+ attn_mask = build_block_causal_mask(
825
+ T,
826
+ H_patches,
827
+ W_patches,
828
+ token_ids=token_ids,
829
+ num_prefix_tokens=1,
830
+ device=x.device,
831
+ )
832
+
833
+ # Fwd prop
834
+ outs = []
835
+ for i, blk in enumerate(self.blocks):
836
+ x = blk(
837
+ x,
838
+ T=T,
839
+ H_patches=H_patches,
840
+ W_patches=W_patches,
841
+ token_ids=token_ids,
842
+ attn_mask=attn_mask,
843
+ )
844
+ if self.out_layers is not None and i in self.out_layers:
845
+ outs.append(self.norm(x))
846
+
847
+ if self.out_layers is not None:
848
+ return outs
849
+
850
+ if self.norm is not None:
851
+ x = self.norm(x)
852
+
853
+ return x
854
+
855
+ def interpolate_pos_encoding(self, x, pos_embed):
856
+ _, N, dim = pos_embed.shape
857
+ cls_pos_embed = pos_embed[:, :1]
858
+ patch_pos_embed = pos_embed[:, 1:]
859
+ N = patch_pos_embed.shape[1]
860
+
861
+ if self.is_video:
862
+ # If pos_embed already correct size, just return.
863
+ _, _, T, H, W = x.shape
864
+ if H == self.input_size and W == self.input_size and T == self.num_frames:
865
+ return pos_embed
866
+
867
+ # Convert depth, height, width of input to be measured in patches
868
+ # instead of pixels/frames.
869
+ T = T // self.tubelet_size
870
+ H = H // self.patch_size
871
+ W = W // self.patch_size
872
+
873
+ # Compute the initialized shape of the positional embedding measured
874
+ # in patches.
875
+ N_t = self.num_frames // self.tubelet_size
876
+ N_h = N_w = self.input_size // self.patch_size
877
+ assert (
878
+ N_h * N_w * N_t == N
879
+ ), "Positional embedding initialized incorrectly"
880
+
881
+ # Compute scale factor for spatio-temporal interpolation.
882
+ scale_factor = (T / N_t, H / N_h, W / N_w)
883
+
884
+ pos_embed = nn.functional.interpolate(
885
+ patch_pos_embed.reshape(1, N_t, N_h, N_w, dim).permute(0, 4, 1, 2, 3),
886
+ scale_factor=scale_factor,
887
+ mode="trilinear",
888
+ )
889
+ patch_pos_embed = pos_embed.permute(0, 2, 3, 4, 1).view(1, -1, dim)
890
+ return torch.cat((cls_pos_embed, patch_pos_embed), dim=1)
891
+
892
+ # If pos_embed already correct size, just return.
893
+ _, _, H, W = x.shape
894
+ if H == self.input_size and W == self.input_size:
895
+ return pos_embed
896
+
897
+ # Compute scale factor for spatial interpolation.
898
+ npatch = (H // self.patch_size) * (W // self.patch_size)
899
+ scale_factor = math.sqrt(npatch / N)
900
+
901
+ pos_embed = nn.functional.interpolate(
902
+ patch_pos_embed.reshape(1, int(math.sqrt(N)), int(math.sqrt(N)), dim).permute(
903
+ 0,
904
+ 3,
905
+ 1,
906
+ 2,
907
+ ),
908
+ scale_factor=scale_factor,
909
+ mode="bicubic",
910
+ )
911
+ patch_pos_embed = pos_embed.permute(0, 2, 3, 1).view(1, -1, dim)
912
+ return torch.cat((cls_pos_embed, patch_pos_embed), dim=1)
913
+
914
+
915
+ def vit_tiny(patch_size=16, **kwargs):
916
+ model = VisionTransformer(
917
+ patch_size=patch_size,
918
+ embed_dim=192,
919
+ depth=12,
920
+ num_heads=3,
921
+ mlp_ratio=4,
922
+ qkv_bias=True,
923
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
924
+ **kwargs,
925
+ )
926
+ return model
927
+
928
+
929
+ def vit_small(patch_size=16, **kwargs):
930
+ model = VisionTransformer(
931
+ patch_size=patch_size,
932
+ embed_dim=384,
933
+ depth=12,
934
+ num_heads=6,
935
+ mlp_ratio=4,
936
+ qkv_bias=True,
937
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
938
+ **kwargs,
939
+ )
940
+ return model
941
+
942
+
943
+ def vit_base(patch_size=16, **kwargs):
944
+ model = VisionTransformer(
945
+ patch_size=patch_size,
946
+ embed_dim=768,
947
+ depth=12,
948
+ num_heads=12,
949
+ mlp_ratio=4,
950
+ qkv_bias=True,
951
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
952
+ **kwargs,
953
+ )
954
+ return model
955
+
956
+
957
+ def vit_large(patch_size=16, **kwargs):
958
+ model = VisionTransformer(
959
+ patch_size=patch_size,
960
+ embed_dim=1024,
961
+ depth=24,
962
+ num_heads=16,
963
+ mlp_ratio=4,
964
+ qkv_bias=True,
965
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
966
+ **kwargs,
967
+ )
968
+ return model
969
+
970
+
971
+ def vit_huge(patch_size=16, **kwargs):
972
+ model = VisionTransformer(
973
+ patch_size=patch_size,
974
+ embed_dim=1280,
975
+ depth=32,
976
+ num_heads=16,
977
+ mlp_ratio=4,
978
+ qkv_bias=True,
979
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
980
+ **kwargs,
981
+ )
982
+ return model
983
+
984
+
985
+ def vit_giant(patch_size=16, **kwargs):
986
+ model = VisionTransformer(
987
+ patch_size=patch_size,
988
+ embed_dim=1408,
989
+ depth=40,
990
+ num_heads=16,
991
+ mlp_ratio=48 / 11,
992
+ qkv_bias=True,
993
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
994
+ **kwargs,
995
+ )
996
+ return model
997
+
998
+
999
+ def vit_gigantic(patch_size=14, **kwargs):
1000
+ model = VisionTransformer(
1001
+ patch_size=patch_size,
1002
+ embed_dim=1664,
1003
+ depth=48,
1004
+ num_heads=16,
1005
+ mpl_ratio=64 / 13,
1006
+ qkv_bias=True,
1007
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
1008
+ **kwargs,
1009
+ )
1010
+ return model
1011
+
1012
+
1013
+ VIT_EMBED_DIMS = {
1014
+ "vit_tiny": 192,
1015
+ "vit_small": 384,
1016
+ "vit_base": 768,
1017
+ "vit_large": 1024,
1018
+ "vit_huge": 1280,
1019
+ "vit_giant": 1408,
1020
+ "vit_gigantic": 1664,
1021
+ }
1022
+
1023
+
1024
+ __all__ = [
1025
+ "SIGReg",
1026
+ "Attention",
1027
+ "Block",
1028
+ "MLP",
1029
+ "PatchEmbed",
1030
+ "PatchEmbed3D",
1031
+ "Projector",
1032
+ "RoPEAttention",
1033
+ "VIT_EMBED_DIMS",
1034
+ "VisionTransformer",
1035
+ "get_1d_sincos_pos_embed",
1036
+ "get_1d_sincos_pos_embed_from_grid",
1037
+ "get_2d_sincos_pos_embed",
1038
+ "get_3d_sincos_pos_embed",
1039
+ "rotate_queries_or_keys",
1040
+ "trunc_normal_",
1041
+ "vit_base",
1042
+ "vit_giant",
1043
+ "vit_gigantic",
1044
+ "vit_huge",
1045
+ "vit_large",
1046
+ "vit_small",
1047
+ "vit_tiny",
1048
+ ]
1049
+
1050
+
1051
+ class LeVJEPAModel(PreTrainedModel):
1052
+ """Frozen-feature video encoder.
1053
+
1054
+ forward(pixel_values) -> BaseModelOutput with
1055
+ last_hidden_state : (B, 1 + N, D) CLS followed by patch tokens
1056
+ pooler_output : (B, D) the CLS token
1057
+
1058
+ pixel_values is (B, C, T, H, W), already normalised. For a still image,
1059
+ repeat it along T -- that is how the ImageNet probe feeds this model.
1060
+ """
1061
+
1062
+ config_class = LeVJEPAConfig
1063
+ base_model_prefix = "encoder"
1064
+ main_input_name = "pixel_values"
1065
+ supports_gradient_checkpointing = False
1066
+
1067
+ def __init__(self, config):
1068
+ super().__init__(config)
1069
+ self.encoder = VisionTransformer(
1070
+ img_size=config.img_size,
1071
+ patch_size=config.patch_size,
1072
+ num_frames=config.num_frames,
1073
+ tubelet_size=config.tubelet_size,
1074
+ in_chans=config.in_chans,
1075
+ embed_dim=config.embed_dim,
1076
+ depth=config.depth,
1077
+ num_heads=config.num_heads,
1078
+ mlp_ratio=config.mlp_ratio,
1079
+ qkv_bias=config.qkv_bias,
1080
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
1081
+ uniform_power=config.uniform_power,
1082
+ use_rope=config.use_rope,
1083
+ token_drop_rate=config.token_drop_rate,
1084
+ token_drop_mode=config.token_drop_mode,
1085
+ token_drop_k=config.token_drop_k,
1086
+ attn_mode=config.attn_mode,
1087
+ )
1088
+ self.post_init()
1089
+
1090
+ def _init_weights(self, module):
1091
+ # Weights always arrive from a checkpoint; the vendored VisionTransformer
1092
+ # already ran its own init at construction time.
1093
+ return
1094
+
1095
+ def forward(self, pixel_values, return_dict=True, **kwargs):
1096
+ hidden = self.encoder(pixel_values)
1097
+ if isinstance(hidden, (list, tuple)):
1098
+ hidden = hidden[-1]
1099
+ pooled = hidden[:, 0]
1100
+ if not return_dict:
1101
+ return (hidden, pooled)
1102
+ out = BaseModelOutput(last_hidden_state=hidden)
1103
+ out["pooler_output"] = pooled
1104
+ return out