Equimo 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Equimo-0.1.0.dist-info/LICENSE.md +21 -0
- Equimo-0.1.0.dist-info/METADATA +104 -0
- Equimo-0.1.0.dist-info/RECORD +29 -0
- Equimo-0.1.0.dist-info/WHEEL +5 -0
- Equimo-0.1.0.dist-info/top_level.txt +1 -0
- equimo/__init__.py +0 -0
- equimo/layers/__init__.py +0 -0
- equimo/layers/attention.py +1526 -0
- equimo/layers/convolution.py +329 -0
- equimo/layers/downsample.py +58 -0
- equimo/layers/dropout.py +217 -0
- equimo/layers/ffn.py +184 -0
- equimo/layers/generic.py +68 -0
- equimo/layers/mamba.py +169 -0
- equimo/layers/norm.py +87 -0
- equimo/layers/patch.py +399 -0
- equimo/layers/posemb.py +416 -0
- equimo/layers/sharing.py +160 -0
- equimo/layers/squeeze_excite.py +143 -0
- equimo/models/__init__.py +7 -0
- equimo/models/emamodel.py +99 -0
- equimo/models/fastervit.py +488 -0
- equimo/models/mlla.py +166 -0
- equimo/models/partialformer.py +407 -0
- equimo/models/shvit.py +376 -0
- equimo/models/vit.py +472 -0
- equimo/models/vssd.py +189 -0
- equimo/ops/scan.py +246 -0
- equimo/utils.py +127 -0
equimo/layers/patch.py
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Callable, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
import equinox as eqx
|
|
5
|
+
import jax
|
|
6
|
+
import jax.numpy as jnp
|
|
7
|
+
import jax.random as jr
|
|
8
|
+
from einops import rearrange
|
|
9
|
+
from jaxtyping import Array, Float, PRNGKeyArray
|
|
10
|
+
|
|
11
|
+
from equimo.layers.convolution import SingleConvBlock
|
|
12
|
+
from equimo.layers.squeeze_excite import SEModule
|
|
13
|
+
from equimo.utils import make_2tuple, nearest_power_of_2_divisor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PatchEmbedding(eqx.Module):
|
|
17
|
+
"""Image to patch embedding module for vision transformers.
|
|
18
|
+
|
|
19
|
+
This module converts an image into a sequence of patch embeddings by:
|
|
20
|
+
1. Splitting the image into fixed-size patches
|
|
21
|
+
2. Projecting each patch to an embedding dimension
|
|
22
|
+
3. Optionally flattening the spatial dimensions
|
|
23
|
+
|
|
24
|
+
Supports dynamic image sizes and padding when needed.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
patch_size: Size of each patch (static)
|
|
28
|
+
img_size: Input image size (static)
|
|
29
|
+
grid_size: Grid dimensions after patching (static)
|
|
30
|
+
num_patches: Total number of patches (static)
|
|
31
|
+
flatten: Whether to flatten spatial dimensions (static)
|
|
32
|
+
dynamic_img_size: Allow variable image sizes (static)
|
|
33
|
+
dynamic_img_pad: Allow padding for non-divisible sizes (static)
|
|
34
|
+
proj: Patch projection layer
|
|
35
|
+
norm: Normalization layer
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
patch_size: int | Tuple[int, int] = eqx.field(static=True)
|
|
39
|
+
|
|
40
|
+
img_size: Optional[Tuple[int, int]] = eqx.field(static=True)
|
|
41
|
+
grid_size: Optional[Tuple[int, int]] = eqx.field(static=True)
|
|
42
|
+
num_patches: Optional[int] = eqx.field(static=True)
|
|
43
|
+
|
|
44
|
+
flatten: bool = eqx.field(static=True)
|
|
45
|
+
dynamic_img_size: bool = eqx.field(static=True)
|
|
46
|
+
dynamic_img_pad: bool = eqx.field(static=True)
|
|
47
|
+
|
|
48
|
+
proj: eqx.nn.Embedding
|
|
49
|
+
norm: eqx.Module
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
in_channels: int,
|
|
54
|
+
dim: int,
|
|
55
|
+
patch_size: int | Tuple[int, int],
|
|
56
|
+
*,
|
|
57
|
+
key: PRNGKeyArray,
|
|
58
|
+
img_size: Optional[int | Tuple[int, int]] = None,
|
|
59
|
+
flatten: bool = True,
|
|
60
|
+
dynamic_img_size: bool = False,
|
|
61
|
+
dynamic_img_pad: bool = False,
|
|
62
|
+
norm_layer: eqx.Module = eqx.nn.Identity,
|
|
63
|
+
**kwargs,
|
|
64
|
+
):
|
|
65
|
+
patch_size = make_2tuple(patch_size)
|
|
66
|
+
self.patch_size = patch_size
|
|
67
|
+
self.flatten = flatten
|
|
68
|
+
|
|
69
|
+
if img_size is None:
|
|
70
|
+
self.img_size = None
|
|
71
|
+
self.grid_size = None
|
|
72
|
+
self.num_patches = None
|
|
73
|
+
else:
|
|
74
|
+
self.img_size = make_2tuple(img_size)
|
|
75
|
+
self.grid_size = (
|
|
76
|
+
self.img_size[0] // patch_size[0],
|
|
77
|
+
self.img_size[1] // patch_size[1],
|
|
78
|
+
)
|
|
79
|
+
self.num_patches = self.grid_size[0] * self.grid_size[1]
|
|
80
|
+
|
|
81
|
+
self.dynamic_img_size = dynamic_img_size
|
|
82
|
+
self.dynamic_img_pad = dynamic_img_pad
|
|
83
|
+
|
|
84
|
+
self.proj = eqx.nn.Conv(
|
|
85
|
+
num_spatial_dims=2,
|
|
86
|
+
in_channels=in_channels,
|
|
87
|
+
out_channels=dim,
|
|
88
|
+
kernel_size=patch_size, # tuple of 2 ints
|
|
89
|
+
stride=patch_size,
|
|
90
|
+
key=key,
|
|
91
|
+
)
|
|
92
|
+
self.norm = norm_layer(dim)
|
|
93
|
+
|
|
94
|
+
def dynamic_feat_size(self, img_size: Tuple[int, int]) -> Tuple[int, int]:
|
|
95
|
+
if self.dynamic_img_pad:
|
|
96
|
+
return math.ceil(img_size[0] / self.patch_size[0]), math.ceil(
|
|
97
|
+
img_size[1] / self.patch_size[1]
|
|
98
|
+
)
|
|
99
|
+
return img_size[0] // self.patch_size[0], img_size[1] // self.patch_size[1]
|
|
100
|
+
|
|
101
|
+
def __call__(self, x: Float[Array, "channels height width"]) -> Float[Array, "..."]:
|
|
102
|
+
C, H, W = x.shape
|
|
103
|
+
|
|
104
|
+
if self.img_size is not None:
|
|
105
|
+
if not self.dynamic_img_size:
|
|
106
|
+
if H != self.img_size[0]:
|
|
107
|
+
raise AssertionError(
|
|
108
|
+
f"Input height ({H}) doesn't match model ({self.img_size[0]})"
|
|
109
|
+
)
|
|
110
|
+
if W != self.img_size[1]:
|
|
111
|
+
raise AssertionError(
|
|
112
|
+
f"Input width ({W}) doesn't match model ({self.img_size[1]})"
|
|
113
|
+
)
|
|
114
|
+
elif not self.dynamic_img_pad:
|
|
115
|
+
if H % self.patch_size[0] != 0:
|
|
116
|
+
raise AssertionError(
|
|
117
|
+
f"Input height ({H}) should be divisible by patch size ({self.patch_size[0]})"
|
|
118
|
+
)
|
|
119
|
+
if W % self.patch_size[1] != 0:
|
|
120
|
+
raise AssertionError(
|
|
121
|
+
f"Input width ({W}) should be divisible by patch size ({self.patch_size[1]})"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if self.dynamic_img_pad:
|
|
125
|
+
pad_h = (self.patch_size[0] - H % self.patch_size[0]) % self.patch_size[0]
|
|
126
|
+
pad_w = (self.patch_size[1] - W % self.patch_size[1]) % self.patch_size[1]
|
|
127
|
+
x = jnp.pad(x, pad_width=((0, 0), (0, pad_h), (0, pad_w)))
|
|
128
|
+
|
|
129
|
+
x = self.proj(x)
|
|
130
|
+
C, H, W = x.shape
|
|
131
|
+
|
|
132
|
+
x = rearrange(x, "c h w -> (h w) c")
|
|
133
|
+
x = jax.vmap(self.norm)(x)
|
|
134
|
+
|
|
135
|
+
if not self.flatten:
|
|
136
|
+
return rearrange(x, "(h w) c -> c h w", h=H, w=W)
|
|
137
|
+
|
|
138
|
+
return x
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ConvPatchEmbed(eqx.Module):
|
|
142
|
+
"""
|
|
143
|
+
Convolutional Patch Embedding, used in MambaVision.
|
|
144
|
+
|
|
145
|
+
This module applies a series of convolutional layers to embed patches.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
in_features (int): Number of input features.
|
|
149
|
+
hidden_features (int): Number of hidden features.
|
|
150
|
+
out_features (int): Number of output features.
|
|
151
|
+
act_layer (Callable, optional): Activation function to use. Defaults to jax.nn.relu.
|
|
152
|
+
norm_layer (Callable, optional): Normalization layer to use. Defaults to eqx.nn.BatchNorm.
|
|
153
|
+
key (PRNGKeyArray): Random number for PRNG.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
conv1: eqx.nn.Conv
|
|
157
|
+
conv2: eqx.nn.Conv
|
|
158
|
+
norm1: eqx.Module
|
|
159
|
+
norm2: eqx.Module
|
|
160
|
+
act: Callable
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
in_channels: int,
|
|
165
|
+
hidden_channels: int,
|
|
166
|
+
out_channels: int,
|
|
167
|
+
*,
|
|
168
|
+
key: PRNGKeyArray,
|
|
169
|
+
act_layer: Callable = jax.nn.relu,
|
|
170
|
+
norm_layer: Callable = eqx.nn.LayerNorm,
|
|
171
|
+
):
|
|
172
|
+
key_conv1, key_conv2 = jr.split(key, 2)
|
|
173
|
+
|
|
174
|
+
self.conv1 = eqx.nn.Conv(
|
|
175
|
+
num_spatial_dims=2,
|
|
176
|
+
in_channels=in_channels,
|
|
177
|
+
out_channels=hidden_channels,
|
|
178
|
+
kernel_size=(3, 3),
|
|
179
|
+
stride=2,
|
|
180
|
+
padding=1,
|
|
181
|
+
use_bias=False,
|
|
182
|
+
key=key_conv1,
|
|
183
|
+
)
|
|
184
|
+
self.norm1 = norm_layer(hidden_channels)
|
|
185
|
+
self.act = act_layer
|
|
186
|
+
self.conv2 = eqx.nn.Conv(
|
|
187
|
+
num_spatial_dims=2,
|
|
188
|
+
in_channels=hidden_channels,
|
|
189
|
+
out_channels=out_channels,
|
|
190
|
+
kernel_size=(3, 3),
|
|
191
|
+
stride=2,
|
|
192
|
+
padding=1,
|
|
193
|
+
use_bias=False,
|
|
194
|
+
key=key_conv2,
|
|
195
|
+
)
|
|
196
|
+
self.norm2 = norm_layer(out_channels)
|
|
197
|
+
|
|
198
|
+
def flatten(
|
|
199
|
+
self, x: Float[Array, "channels height width"]
|
|
200
|
+
) -> Float[Array, "space channels"]:
|
|
201
|
+
return rearrange(x, "c h w -> (h w) c")
|
|
202
|
+
|
|
203
|
+
def deflatten(
|
|
204
|
+
self,
|
|
205
|
+
x: Float[Array, "space channels"],
|
|
206
|
+
h: int,
|
|
207
|
+
w: int,
|
|
208
|
+
) -> Float[Array, "channels height width"]:
|
|
209
|
+
return rearrange(x, "(h w) c -> c h w", h=h, w=w)
|
|
210
|
+
|
|
211
|
+
def __call__(
|
|
212
|
+
self, x: Float[Array, "channels height width"]
|
|
213
|
+
) -> Float[Array, "new_channels new_height new_width"]:
|
|
214
|
+
"""
|
|
215
|
+
Forward pass of the ConvPatchEmbed module.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
x (jnp.ndarray): Input tensor.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
jnp.ndarray: Output tensor after applying convolutional patch embedding.
|
|
222
|
+
"""
|
|
223
|
+
c, h, w = x.shape
|
|
224
|
+
x = self.deflatten(
|
|
225
|
+
self.act(jax.vmap(self.norm1)(self.flatten(self.conv1(x)))),
|
|
226
|
+
h=h // 2,
|
|
227
|
+
w=w // 2,
|
|
228
|
+
)
|
|
229
|
+
x = self.deflatten(
|
|
230
|
+
self.act(jax.vmap(self.norm2)(self.flatten(self.conv2(x)))),
|
|
231
|
+
h=h // 4,
|
|
232
|
+
w=w // 4,
|
|
233
|
+
)
|
|
234
|
+
return x
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class PatchMerging(eqx.Module):
|
|
238
|
+
"""Patch merging module that reduces spatial resolution while increasing channels.
|
|
239
|
+
|
|
240
|
+
This module implements a hierarchical feature aggregation using three convolution stages:
|
|
241
|
+
1. 1x1 conv to expand channels
|
|
242
|
+
2. 3x3 depthwise conv with stride 2 for spatial reduction
|
|
243
|
+
3. 1x1 conv to adjust final channel dimension
|
|
244
|
+
|
|
245
|
+
The module follows an inverted bottleneck architecture with expansion ratio.
|
|
246
|
+
|
|
247
|
+
Attributes:
|
|
248
|
+
conv1: First 1x1 convolution for channel expansion
|
|
249
|
+
conv2: 3x3 depthwise convolution for spatial reduction
|
|
250
|
+
conv3: Final 1x1 convolution for channel adjustment
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
conv1: eqx.Module
|
|
254
|
+
conv2: eqx.Module
|
|
255
|
+
conv3: eqx.Module
|
|
256
|
+
|
|
257
|
+
def __init__(
|
|
258
|
+
self,
|
|
259
|
+
dim: int,
|
|
260
|
+
*,
|
|
261
|
+
key: PRNGKeyArray,
|
|
262
|
+
ratio: float = 4.0,
|
|
263
|
+
**kwargs,
|
|
264
|
+
):
|
|
265
|
+
(
|
|
266
|
+
key_conv1,
|
|
267
|
+
key_conv2,
|
|
268
|
+
key_conv3,
|
|
269
|
+
) = jr.split(key, 3)
|
|
270
|
+
out_dim = int(dim * 2)
|
|
271
|
+
hidden_dim = int(out_dim * ratio)
|
|
272
|
+
|
|
273
|
+
self.conv1 = SingleConvBlock(
|
|
274
|
+
in_channels=dim,
|
|
275
|
+
out_channels=hidden_dim,
|
|
276
|
+
kernel_size=1,
|
|
277
|
+
stride=1,
|
|
278
|
+
padding=0,
|
|
279
|
+
use_norm=False,
|
|
280
|
+
act_layer=jax.nn.relu,
|
|
281
|
+
key=key_conv1,
|
|
282
|
+
)
|
|
283
|
+
self.conv2 = SingleConvBlock(
|
|
284
|
+
in_channels=hidden_dim,
|
|
285
|
+
out_channels=hidden_dim,
|
|
286
|
+
kernel_size=3,
|
|
287
|
+
stride=2,
|
|
288
|
+
padding=1,
|
|
289
|
+
groups=hidden_dim,
|
|
290
|
+
use_norm=False,
|
|
291
|
+
act_layer=jax.nn.relu,
|
|
292
|
+
key=key_conv2,
|
|
293
|
+
)
|
|
294
|
+
self.conv3 = SingleConvBlock(
|
|
295
|
+
in_channels=hidden_dim,
|
|
296
|
+
out_channels=out_dim,
|
|
297
|
+
kernel_size=1,
|
|
298
|
+
stride=1,
|
|
299
|
+
padding=0,
|
|
300
|
+
use_norm=False,
|
|
301
|
+
act_layer=jax.nn.relu,
|
|
302
|
+
key=key_conv3,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def __call__(self, x: Float[Array, "seqlen dim"]) -> Float[Array, "new_seqlen dim"]:
|
|
306
|
+
l, _ = x.shape
|
|
307
|
+
h = w = int(l**0.5)
|
|
308
|
+
|
|
309
|
+
x = rearrange(x, "(h w) c -> c h w", h=h, w=w)
|
|
310
|
+
x = self.conv3(self.conv2(self.conv1(x)))
|
|
311
|
+
x = rearrange(x, "c h w -> (h w) c")
|
|
312
|
+
|
|
313
|
+
return x
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class SEPatchMerging(eqx.Module):
|
|
317
|
+
"""Squeeze-and-Excite Patch Merging module.
|
|
318
|
+
|
|
319
|
+
This module combines patch merging with squeeze-and-excitation attention.
|
|
320
|
+
The architecture consists of:
|
|
321
|
+
1. Channel expansion through 1x1 conv
|
|
322
|
+
2. Spatial reduction with 3x3 conv
|
|
323
|
+
3. SE attention module for channel recalibration
|
|
324
|
+
4. Channel projection with 1x1 conv
|
|
325
|
+
Each conv layer is followed by group normalization and ReLU activation.
|
|
326
|
+
|
|
327
|
+
Attributes:
|
|
328
|
+
conv1: First 1x1 convolution layer
|
|
329
|
+
conv2: 3x3 convolution layer for spatial reduction
|
|
330
|
+
conv3: Final 1x1 convolution layer
|
|
331
|
+
norm1: Group normalization after first conv
|
|
332
|
+
norm2: Group normalization after second conv
|
|
333
|
+
norm3: Group normalization after third conv
|
|
334
|
+
se: Squeeze-and-Excitation module
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
conv1: eqx.nn.Conv
|
|
338
|
+
conv2: eqx.nn.Conv
|
|
339
|
+
conv3: eqx.nn.Conv
|
|
340
|
+
norm1: eqx.Module
|
|
341
|
+
norm2: eqx.Module
|
|
342
|
+
norm3: eqx.Module
|
|
343
|
+
se: SEModule
|
|
344
|
+
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
dim: int,
|
|
348
|
+
out_dim: int,
|
|
349
|
+
*,
|
|
350
|
+
key: PRNGKeyArray,
|
|
351
|
+
norm_max_group: int = 32,
|
|
352
|
+
**kwargs,
|
|
353
|
+
):
|
|
354
|
+
key_conv1, key_conv2, key_conv3, key_se = jr.split(key, 4)
|
|
355
|
+
hidden_dim = int(dim * 4)
|
|
356
|
+
num_groups = nearest_power_of_2_divisor(hidden_dim, norm_max_group)
|
|
357
|
+
|
|
358
|
+
self.conv1 = eqx.nn.Conv(
|
|
359
|
+
num_spatial_dims=2,
|
|
360
|
+
in_channels=dim,
|
|
361
|
+
out_channels=hidden_dim,
|
|
362
|
+
kernel_size=1,
|
|
363
|
+
stride=1,
|
|
364
|
+
padding=0,
|
|
365
|
+
key=key_conv1,
|
|
366
|
+
)
|
|
367
|
+
self.norm1 = eqx.nn.GroupNorm(num_groups, hidden_dim)
|
|
368
|
+
self.conv2 = eqx.nn.Conv(
|
|
369
|
+
num_spatial_dims=2,
|
|
370
|
+
in_channels=hidden_dim,
|
|
371
|
+
out_channels=hidden_dim,
|
|
372
|
+
kernel_size=3,
|
|
373
|
+
stride=2,
|
|
374
|
+
padding=1,
|
|
375
|
+
key=key_conv2,
|
|
376
|
+
)
|
|
377
|
+
self.norm2 = eqx.nn.GroupNorm(num_groups, hidden_dim)
|
|
378
|
+
self.se = SEModule(hidden_dim, rd_ratio=1.0 / 4, key=key_se)
|
|
379
|
+
self.conv3 = eqx.nn.Conv(
|
|
380
|
+
num_spatial_dims=2,
|
|
381
|
+
in_channels=hidden_dim,
|
|
382
|
+
out_channels=out_dim,
|
|
383
|
+
kernel_size=1,
|
|
384
|
+
stride=1,
|
|
385
|
+
padding=0,
|
|
386
|
+
key=key_conv3,
|
|
387
|
+
)
|
|
388
|
+
self.norm3 = eqx.nn.GroupNorm(
|
|
389
|
+
nearest_power_of_2_divisor(out_dim, norm_max_group),
|
|
390
|
+
out_dim,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def __call__(self, x: Float[Array, "channels height width"]) -> Float[Array, "..."]:
|
|
394
|
+
x = jax.nn.relu(self.norm1(self.conv1(x)))
|
|
395
|
+
x = jax.nn.relu(self.norm2(self.conv2(x)))
|
|
396
|
+
x = self.se(x)
|
|
397
|
+
x = self.norm3(self.conv3(x))
|
|
398
|
+
|
|
399
|
+
return x
|