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/models/mlla.py ADDED
@@ -0,0 +1,166 @@
1
+ from typing import List
2
+
3
+ import equinox as eqx
4
+ import jax
5
+ import jax.numpy as jnp
6
+ import jax.random as jr
7
+ from einops import reduce
8
+ from jaxtyping import Array, Float, PRNGKeyArray
9
+
10
+ from equimo.layers.attention import LinearAttention, MllaBlock
11
+ from equimo.layers.convolution import Stem
12
+ from equimo.layers.ffn import Mlp
13
+ from equimo.layers.patch import PatchMerging
14
+ from equimo.models.vit import BlockChunk
15
+ from equimo.utils import to_list
16
+
17
+
18
+ class Mlla(eqx.Module):
19
+ """Mamba-like Linear Attention (MLLA) Vision Model[1].
20
+
21
+ A vision transformer architecture that combines linear attention mechanisms
22
+ inspired by Mamba with hierarchical feature processing. The model processes
23
+ images through patches, applies position-aware dropouts, and uses a series
24
+ of attention blocks with progressive feature resolution reduction.
25
+
26
+ Attributes:
27
+ num_features: Number of features in the final layer
28
+ patch_embed: Patch embedding layer (Stem)
29
+ pos_drop: Positional dropout layer
30
+ blocks: List of processing blocks
31
+ head: Classification head
32
+
33
+ References:
34
+ [1]: Han, et al., 2024. https://arxiv.org/abs/2405.16605
35
+ """
36
+
37
+ num_features: int = eqx.field(static=True)
38
+
39
+ patch_embed: eqx.Module
40
+ pos_drop: eqx.Module
41
+ blocks: List[eqx.Module]
42
+ head: eqx.Module
43
+
44
+ def __init__(
45
+ self,
46
+ img_size: int,
47
+ in_channels: int,
48
+ *,
49
+ key: PRNGKeyArray,
50
+ repeat: int = 1,
51
+ dim: int = 96,
52
+ patch_size: int = 4,
53
+ depths: List[int] = [2, 2, 6, 2],
54
+ num_heads: List[int] = [3, 6, 12, 24],
55
+ attentions_layers: List[eqx.Module] | eqx.Module = LinearAttention,
56
+ drop_rate: float = 0.0,
57
+ drop_path_rate: float = 0.0,
58
+ drop_path_uniform: bool = False,
59
+ mlp_ratio: float = 4.0,
60
+ num_classes: int | None = 1000,
61
+ **kwargs,
62
+ ):
63
+ """Initialize the MLLA model.
64
+
65
+ Args:
66
+ img_size: Input image size
67
+ in_channels: Number of input channels
68
+ key: PRNG key for random operations
69
+ repeat: Number of times to repeat each block
70
+ dim: Initial embedding dimension
71
+ patch_size: Size of image patches
72
+ depths: Number of blocks at each stage
73
+ num_heads: Number of attention heads at each stage
74
+ attentions_layers: Type of attention layer(s) to use
75
+ drop_rate: Dropout rate
76
+ drop_path_rate: Drop path rate
77
+ drop_path_uniform: Whether to use uniform drop path rates
78
+ mlp_ratio: MLP expansion ratio
79
+ num_classes: Number of output classes (None for feature extraction)
80
+ """
81
+ key_stem, key_head, *block_subkeys = jr.split(key, 2 + len(depths))
82
+
83
+ n_chunks = len(depths)
84
+ self.num_features = int(dim * 2 ** (n_chunks - 1))
85
+
86
+ self.patch_embed = Stem(
87
+ in_channels=in_channels,
88
+ img_size=img_size,
89
+ patch_size=patch_size,
90
+ embed_dim=dim,
91
+ key=key_stem,
92
+ )
93
+ patches_resolution = self.patch_embed.patches_resolution
94
+
95
+ self.pos_drop = eqx.nn.Dropout(drop_rate)
96
+
97
+ dpr = (
98
+ list(jnp.linspace(0.0, drop_path_rate, n_chunks))
99
+ if not drop_path_uniform
100
+ else to_list(drop_path_rate, n_chunks)
101
+ )
102
+
103
+ num_heads = to_list(num_heads, n_chunks)
104
+ attentions_layers = to_list(attentions_layers, n_chunks)
105
+ self.blocks = [
106
+ BlockChunk(
107
+ block=MllaBlock,
108
+ repeat=repeat,
109
+ depth=depth,
110
+ downsampler=PatchMerging if (i < n_chunks - 1) else eqx.nn.Identity,
111
+ downsampler_contains_dropout=False,
112
+ dim=int(dim * 2**i),
113
+ input_resolution=(
114
+ patches_resolution[0] // (2**i),
115
+ patches_resolution[1] // (2**i),
116
+ ),
117
+ num_heads=num_heads[i],
118
+ act_layer=jax.nn.silu,
119
+ use_dwc=True,
120
+ attention_layer=attentions_layers[i],
121
+ drop_path=dpr[i],
122
+ mlp_ratio=mlp_ratio,
123
+ ffn_layer=Mlp,
124
+ key=block_subkeys[i],
125
+ )
126
+ for i, depth in enumerate(depths)
127
+ ]
128
+
129
+ self.head = (
130
+ eqx.nn.Sequential(
131
+ [
132
+ eqx.nn.LayerNorm(self.num_features),
133
+ eqx.nn.Linear(self.num_features, num_classes, key=key_head),
134
+ ]
135
+ )
136
+ if num_classes > 0
137
+ else eqx.nn.Identity()
138
+ )
139
+
140
+ def __call__(
141
+ self,
142
+ x: Float[Array, "..."],
143
+ enable_dropout: bool,
144
+ key: PRNGKeyArray,
145
+ ) -> Float[Array, "..."]:
146
+ """Process input through the MLLA model.
147
+
148
+ Args:
149
+ x: Input tensor (typically an image)
150
+ enable_dropout: Whether to enable dropout during inference
151
+ key: PRNG key for random operations
152
+
153
+ Returns:
154
+ Output tensor (class logits if num_classes > 0,
155
+ otherwise feature representations)
156
+ """
157
+ key_pd, *keys = jr.split(key, 1 + len(self.blocks))
158
+
159
+ x = self.patch_embed(x)
160
+ x = self.pos_drop(x, inference=not enable_dropout, key=key_pd)
161
+ for i, blk in enumerate(self.blocks):
162
+ x = blk(x, enable_dropout=enable_dropout, key=keys[i])
163
+ x = reduce(x, "s d -> d", "mean")
164
+ x = self.head(x)
165
+
166
+ return x
@@ -0,0 +1,407 @@
1
+ from typing import Callable, List, Tuple
2
+
3
+ import equinox as eqx
4
+ import jax
5
+ import jax.numpy as jnp
6
+ import jax.random as jr
7
+ from einops import rearrange, reduce
8
+ from jaxtyping import Array, Float, PRNGKeyArray
9
+
10
+ from equimo.layers.attention import PartialFormerBlock
11
+ from equimo.layers.convolution import Stem
12
+ from equimo.layers.dropout import Dropout
13
+ from equimo.layers.ffn import Mlp
14
+ from equimo.layers.patch import PatchMerging
15
+ from equimo.layers.posemb import PosCNN
16
+ from equimo.layers.sharing import LayerSharing
17
+ from equimo.utils import to_list
18
+
19
+
20
+ class LayerSharingWithQA(LayerSharing):
21
+ """Layer sharing implementation with Query Attention (QA) token support.
22
+
23
+ Extends LayerSharing to handle query attention tokens while maintaining
24
+ layer sharing functionality. Processes both input features and QA tokens
25
+ through shared layers with optional LoRA adaptations.
26
+ """
27
+
28
+ def __call__(
29
+ self,
30
+ x: Array,
31
+ qa: Array,
32
+ *args,
33
+ enable_dropout: bool,
34
+ key: PRNGKeyArray,
35
+ **kwargs,
36
+ ):
37
+ if self.repeat == 1:
38
+ return self.f(
39
+ x,
40
+ *args,
41
+ enable_dropout=enable_dropout,
42
+ key=key,
43
+ **kwargs,
44
+ )
45
+
46
+ keys = jr.split(key, self.repeat)
47
+ reshape = len(x.shape) == 3
48
+
49
+ for i in range(self.repeat):
50
+ if reshape:
51
+ _, h, w = x.shape
52
+ lora_x = rearrange(x, "c h w -> (h w) c")
53
+ else:
54
+ lora_x = x
55
+ lora_output = self.dropouts[i](
56
+ jax.vmap(self.loras[i])(lora_x),
57
+ inference=not enable_dropout,
58
+ key=keys[i],
59
+ )
60
+ if reshape:
61
+ lora_output = rearrange(
62
+ lora_output,
63
+ "(h w) c -> c h w",
64
+ h=h,
65
+ w=w,
66
+ )
67
+
68
+ x, qa = self.f(
69
+ x,
70
+ qa=qa,
71
+ enable_dropout=enable_dropout,
72
+ key=key,
73
+ **kwargs,
74
+ )
75
+
76
+ x += lora_output
77
+
78
+ return x, qa
79
+
80
+
81
+ class BlockChunk(eqx.Module):
82
+ """A chunk of processing blocks with query attention and downsampling support.
83
+
84
+ Processes input features through a sequence of attention blocks while
85
+ maintaining and updating a query attention token. Includes positional
86
+ embeddings, downsampling, and QA token projection capabilities.
87
+
88
+ Attributes:
89
+ reshape: Whether to reshape inputs for processing
90
+ downsampler_contains_dropout: If downsampler has dropout
91
+ posemb: Positional embedding layer
92
+ blocks: List of processing blocks
93
+ downsample: Downsampling layer
94
+ qa_proj: Query attention projection layer
95
+ qa_drop: Dropout for query attention
96
+ """
97
+
98
+ reshape: bool = eqx.field(static=True)
99
+ downsampler_contains_dropout: bool = eqx.field(static=True)
100
+
101
+ posemb: eqx.Module
102
+ blocks: List[eqx.Module]
103
+ downsample: eqx.Module
104
+ qa_proj: eqx.Module
105
+ qa_drop: eqx.Module
106
+
107
+ def __init__(
108
+ self,
109
+ depth: int,
110
+ *,
111
+ key: PRNGKeyArray,
112
+ block: eqx.Module = PartialFormerBlock,
113
+ use_cpe: bool = False,
114
+ qa_act_layer: Callable = jax.nn.relu,
115
+ qa_norm_layer: eqx.Module = eqx.nn.LayerNorm,
116
+ qa_drop: float = 0.0,
117
+ repeat: int = 1,
118
+ downsampler: eqx.Module = eqx.nn.Identity,
119
+ downsampler_contains_dropout: bool = False,
120
+ downsampler_kwargs: dict = {},
121
+ **kwargs,
122
+ ):
123
+ key_ds, key_pos, key_qaproj, *block_subkeys = jr.split(key, depth + 3)
124
+ if not isinstance(downsampler, eqx.nn.Identity) or use_cpe:
125
+ if kwargs.get("dim") is None:
126
+ raise ValueError(
127
+ "Using a downsampler or a CPE requires passing a `dim` argument."
128
+ )
129
+
130
+ # self.reshape = block is not ConvBlock
131
+ self.reshape = True # TODO
132
+ self.downsampler_contains_dropout = downsampler_contains_dropout
133
+
134
+ keys_to_spread = [
135
+ k for k, v in kwargs.items() if isinstance(v, list) and len(v) == depth
136
+ ]
137
+
138
+ dim = kwargs.get("dim")
139
+ self.posemb = (
140
+ PosCNN(
141
+ dim,
142
+ dim,
143
+ key=key_pos,
144
+ )
145
+ if use_cpe
146
+ else eqx.nn.Identity()
147
+ )
148
+
149
+ blocks = []
150
+ for i in range(depth):
151
+ config = kwargs | {k: kwargs[k][i] for k in keys_to_spread}
152
+ blocks.append(
153
+ LayerSharingWithQA(
154
+ dim=dim,
155
+ f=block(**config, key=block_subkeys[i]),
156
+ repeat=repeat,
157
+ key=block_subkeys[i],
158
+ ),
159
+ )
160
+ self.blocks = blocks
161
+
162
+ self.downsample = downsampler(dim=dim, **downsampler_kwargs, key=key_ds)
163
+ self.qa_proj = (
164
+ eqx.nn.Sequential(
165
+ [
166
+ eqx.nn.Linear(
167
+ dim,
168
+ dim * 2 if downsampler is not eqx.nn.Identity else dim,
169
+ key=key_qaproj,
170
+ ),
171
+ qa_norm_layer(dim * 2),
172
+ eqx.nn.Lambda(qa_act_layer),
173
+ ]
174
+ )
175
+ if downsampler is not eqx.nn.Identity
176
+ else eqx.nn.Identity()
177
+ )
178
+ self.qa_drop = eqx.nn.Dropout(qa_drop)
179
+
180
+ def __call__(
181
+ self,
182
+ x: Float[Array, "seqlen dim"],
183
+ qa: Float[Array, "1 dim"],
184
+ *,
185
+ enable_dropout: bool,
186
+ key: PRNGKeyArray,
187
+ **kwargs,
188
+ ) -> Tuple[Float[Array, "..."], Float[Array, "..."]]:
189
+ """Process input features and query attention token.
190
+
191
+ Args:
192
+ x: Input feature tensor
193
+ qa: Query attention token
194
+ enable_dropout: Whether to enable dropout
195
+ key: PRNG key for random operations
196
+
197
+ Returns:
198
+ Tuple of (processed features, updated query attention token)
199
+ """
200
+ key_qadrop, *keys = jr.split(key, len(self.blocks) + 1)
201
+
202
+ x = self.posemb(x)
203
+
204
+ for blk, key_block in zip(self.blocks, keys):
205
+ x, qa = blk(
206
+ x, qa=qa, enable_dropout=enable_dropout, key=key_block, **kwargs
207
+ )
208
+
209
+ if self.downsampler_contains_dropout:
210
+ x = self.downsample(x, enable_dropout, key)
211
+ else:
212
+ x = self.downsample(x)
213
+ qa = self.qa_drop(
214
+ jax.vmap(self.qa_proj)(qa),
215
+ inference=not enable_dropout,
216
+ key=key_qadrop,
217
+ )
218
+
219
+ return x, qa
220
+
221
+
222
+ class PartialFormer(eqx.Module):
223
+ """PartialFormer implementation with a Partial Attention mechanism[1].
224
+
225
+ A vision transformer that processes images through patches while using
226
+ a query attention token to guide feature extraction. Combines hierarchical
227
+ feature processing with query-based attention mechanisms.
228
+
229
+ Attributes:
230
+ num_features: Number of features in final layer
231
+ qa_token: Query attention token
232
+ patch_embed: Patch embedding layer
233
+ pos_drop: Positional dropout
234
+ blocks: Processing blocks
235
+ norm: Normalization layer
236
+ head: Classification head
237
+
238
+ Notes:
239
+ WARNING, the original paper[1] does not provide an official
240
+ implementation. This implementation if an interpretation of the paper.
241
+ Although I made my best to follow what I read, it may not be a 1:1
242
+ replica.
243
+
244
+ References:
245
+ [1]: Vo, et al., 2024. https://eccv.ecva.net/virtual/2024/poster/1877
246
+ """
247
+
248
+ num_features: int = eqx.field(static=True)
249
+
250
+ qa_token: jnp.ndarray
251
+ patch_embed: Stem
252
+ pos_drop: Dropout
253
+ blocks: List[eqx.Module]
254
+ norm: eqx.Module
255
+ head: eqx.Module
256
+
257
+ def __init__(
258
+ self,
259
+ img_size: int,
260
+ in_channels: int,
261
+ dim: int,
262
+ num_heads: int | List[int],
263
+ depths: List[int],
264
+ foreground_ratios: Tuple[float, float] | float,
265
+ *,
266
+ key: PRNGKeyArray,
267
+ patch_size: int = 7,
268
+ pos_drop_rate: float = 0.0,
269
+ drop_path_rate: float = 0.0,
270
+ drop_path_uniform: bool = False,
271
+ block: eqx.Module = PartialFormerBlock,
272
+ repeat: int = 1,
273
+ head_expand_ratio: float = 4.0,
274
+ mlp_ratio: float = 4.0,
275
+ qkv_bias: bool = True,
276
+ proj_bias: bool = True,
277
+ proj_ratio: float = 4.0,
278
+ qk_norm: bool = False,
279
+ attn_drop: float = 0.0,
280
+ proj_drop: float = 0.0,
281
+ act_layer: Callable = jax.nn.gelu,
282
+ ffn_layer: eqx.Module = Mlp,
283
+ ffn_bias: bool = True,
284
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
285
+ init_values: float | None = None,
286
+ num_classes: int = 1000,
287
+ interpolate_antialias: bool = False,
288
+ **kwargs,
289
+ ):
290
+ depth = sum(depths)
291
+ key_qa, key_stem, key_head, *block_subkeys = jr.split(key, 3 + len(depths))
292
+
293
+ self.qa_token = jr.uniform(key_qa, (1, dim))
294
+
295
+ self.patch_embed = Stem(
296
+ in_channels=in_channels,
297
+ img_size=img_size,
298
+ patch_size=patch_size,
299
+ embed_dim=dim,
300
+ key=key_stem,
301
+ )
302
+
303
+ self.pos_drop = Dropout(pos_drop_rate)
304
+
305
+ if drop_path_uniform:
306
+ dpr = [drop_path_rate] * depth
307
+ else:
308
+ dpr = list(jnp.linspace(0.0, drop_path_rate, depth))
309
+
310
+ if isinstance(foreground_ratios, float):
311
+ f_ratios = [foreground_ratios] * depth
312
+ elif isinstance(foreground_ratios, Tuple[float, float]):
313
+ f_ratios = list(jnp.linspace(*foreground_ratios, depth))
314
+ else:
315
+ raise ValueError("Unknown type for forefround_ratios, got:")
316
+
317
+ n_chunks = len(depths)
318
+ num_heads = to_list(num_heads, n_chunks)
319
+ self.num_features = int(dim * 2 ** (n_chunks - 1))
320
+ self.blocks = [
321
+ BlockChunk(
322
+ block=block,
323
+ repeat=repeat,
324
+ dim=int(dim * 2**i),
325
+ depth=depths[i],
326
+ use_cpe=False,
327
+ downsampler=PatchMerging if (i < n_chunks - 1) else eqx.nn.Identity,
328
+ downsampler_contains_dropout=False,
329
+ foreground_ratio=f_ratios[i],
330
+ patch_size=patch_size,
331
+ num_heads=num_heads[i],
332
+ head_expand_ratio=head_expand_ratio,
333
+ mlp_ratio=mlp_ratio,
334
+ drop_path=dpr[sum(depths[:i]) : sum(depths[: i + 1])],
335
+ qkv_bias=qkv_bias,
336
+ proj_bias=proj_bias,
337
+ qk_norm=qk_norm,
338
+ attn_drop=attn_drop,
339
+ proj_drop=proj_drop,
340
+ proj_ratio=proj_ratio,
341
+ act_layer=act_layer,
342
+ ffn_layer=ffn_layer,
343
+ ffn_bias=ffn_bias,
344
+ norm_layer=norm_layer,
345
+ init_values=init_values,
346
+ key=block_subkeys[i],
347
+ )
348
+ for i, depth in enumerate(depths)
349
+ ]
350
+
351
+ self.norm = norm_layer(self.num_features)
352
+ self.head = eqx.nn.Linear(self.num_features, num_classes, key=key_head)
353
+
354
+ def features(
355
+ self,
356
+ x: Float[Array, "channels height width"],
357
+ enable_dropout: bool,
358
+ key: PRNGKeyArray,
359
+ ) -> Float[Array, "seqlen dim"]:
360
+ """Extract features from input image using partial attention.
361
+
362
+ Args:
363
+ x: Input image tensor
364
+ enable_dropout: Whether to enable dropout during inference
365
+ key: PRNG key for random operations
366
+
367
+ Returns:
368
+ Tuple of (processed features, final query attention token)
369
+ """
370
+ key_posdrop, *block_subkeys = jr.split(key, len(self.blocks) + 1)
371
+ x = self.patch_embed(x)
372
+ x = self.pos_drop(x, inference=not enable_dropout, key=key_posdrop)
373
+
374
+ qa = self.qa_token
375
+ for blk, key_block in zip(self.blocks, block_subkeys):
376
+ x, qa = blk(
377
+ x,
378
+ qa=qa,
379
+ enable_dropout=enable_dropout,
380
+ key=key_block,
381
+ )
382
+
383
+ return x, qa
384
+
385
+ def __call__(
386
+ self,
387
+ x: Float[Array, "channels height width"],
388
+ enable_dropout: bool,
389
+ key: PRNGKeyArray,
390
+ ) -> Float[Array, "num_classes"]:
391
+ """Process input image through the full network.
392
+
393
+ Args:
394
+ x: Input image tensor
395
+ enable_dropout: Whether to enable dropout during inference
396
+ key: PRNG key for random operations
397
+
398
+ Returns:
399
+ Classification logits for each class
400
+ """
401
+ x, qa = self.features(x, enable_dropout, key)
402
+ x = jax.vmap(self.norm)(x)
403
+ x = reduce(x, "n c -> c", "mean")
404
+
405
+ x = self.head(x)
406
+
407
+ return x