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/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