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.
@@ -0,0 +1,1526 @@
1
+ from typing import Callable, List, Optional, 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.dropout import Dropout, DropPathAdd
11
+ from equimo.layers.ffn import Mlp
12
+ from equimo.layers.mamba import Mamba2Mixer
13
+ from equimo.layers.norm import LayerScale
14
+ from equimo.layers.posemb import PosCNN2D, PosEmbMLPSwinv1D, PosEmbMLPSwinv2D, RoPE
15
+ from equimo.utils import nearest_power_of_2_divisor
16
+
17
+
18
+ class Attention(eqx.Module):
19
+ """Multi-head self attention module.
20
+
21
+ A standard transformer-style attention implementation with query, key and value
22
+ projections followed by scaled dot-product attention.
23
+
24
+ Attributes:
25
+ dim: Total dimension of the input/output
26
+ num_heads: Number of attention heads
27
+ head_dim: Dimension of each attention head (dim // num_heads)
28
+ """
29
+
30
+ dim: int = eqx.field(static=True)
31
+ num_heads: int = eqx.field(static=True)
32
+ head_dim: int = eqx.field(static=True)
33
+
34
+ qkv: eqx.nn.Linear
35
+ proj: eqx.nn.Linear
36
+ q_norm: eqx.Module
37
+ k_norm: eqx.Module
38
+ attn_drop: Dropout
39
+ proj_drop: Dropout
40
+
41
+ def __init__(
42
+ self,
43
+ dim: int,
44
+ num_heads: int,
45
+ *,
46
+ key: PRNGKeyArray,
47
+ qkv_bias: bool = True,
48
+ proj_bias: bool = True,
49
+ qk_norm: bool = False,
50
+ attn_drop: float = 0.0,
51
+ proj_drop: float = 0.0,
52
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
53
+ **kwargs,
54
+ ):
55
+ self.num_heads = num_heads
56
+ self.head_dim = dim // num_heads
57
+ self.dim = dim
58
+
59
+ assert self.head_dim * num_heads == dim, "dim must be divisible by num_heads"
60
+
61
+ key_qkv, key_proj = jr.split(key, 2)
62
+ self.qkv = eqx.nn.Linear(dim, dim * 3, use_bias=qkv_bias, key=key_qkv)
63
+ self.proj = eqx.nn.Linear(dim, dim, use_bias=proj_bias, key=key_proj)
64
+
65
+ self.q_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
66
+ self.k_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
67
+
68
+ self.attn_drop = Dropout(attn_drop)
69
+ self.proj_drop = Dropout(proj_drop)
70
+
71
+ def __call__(
72
+ self,
73
+ x: Float[Array, "seqlen dim"],
74
+ enable_dropout: bool,
75
+ key: PRNGKeyArray,
76
+ ) -> Float[Array, "seqlen dim"]:
77
+ key1, key2 = jr.split(key, 2)
78
+
79
+ qkv = jax.vmap(self.qkv)(x)
80
+ qkv = rearrange(
81
+ qkv,
82
+ "s (n h d) -> n h s d",
83
+ n=3,
84
+ h=self.num_heads,
85
+ d=self.dim // self.num_heads,
86
+ )
87
+ q, k, v = qkv
88
+ q = jax.vmap(jax.vmap(self.q_norm))(q)
89
+ k = jax.vmap(jax.vmap(self.k_norm))(k)
90
+
91
+ attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
92
+ attn = jax.nn.softmax(attn, axis=-1)
93
+ attn = self.attn_drop(attn, inference=not enable_dropout, key=key1)
94
+
95
+ x = jnp.einsum("hqk,hvd->hqd", attn, v)
96
+ x = rearrange(x, "h s d -> s (h d)")
97
+ x = jax.vmap(self.proj)(x)
98
+ x = self.proj_drop(x, inference=not enable_dropout, key=key2)
99
+
100
+ return x
101
+
102
+
103
+ class WindowedAttention(eqx.Module):
104
+ """Windowed multi-head self attention module.
105
+
106
+ Applies self-attention within local windows of the input sequence.
107
+ Includes relative position embeddings within each window.
108
+
109
+ Attributes:
110
+ dim: Total dimension of the input/output
111
+ num_heads: Number of attention heads
112
+ head_dim: Dimension of each attention head (dim // num_heads)
113
+ resolution: Size of each attention window (window_size)
114
+ """
115
+
116
+ dim: int = eqx.field(static=True)
117
+ num_heads: int = eqx.field(static=True)
118
+ head_dim: int = eqx.field(static=True)
119
+ resolution: int = eqx.field(static=True)
120
+
121
+ qkv: eqx.nn.Linear
122
+ proj: eqx.nn.Linear
123
+ q_norm: eqx.Module
124
+ k_norm: eqx.Module
125
+ attn_drop: Dropout
126
+ proj_drop: Dropout
127
+ pos_emb_funct: PosEmbMLPSwinv2D
128
+
129
+ def __init__(
130
+ self,
131
+ dim: int,
132
+ num_heads: int,
133
+ resolution: int,
134
+ seq_len: int,
135
+ *,
136
+ key: PRNGKeyArray,
137
+ qkv_bias: bool = True,
138
+ proj_bias: bool = True,
139
+ qk_norm: bool = False,
140
+ attn_drop: float = 0.0,
141
+ proj_drop: float = 0.0,
142
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
143
+ **kwargs,
144
+ ):
145
+ self.num_heads = num_heads
146
+ self.head_dim = dim // num_heads
147
+ self.dim = dim
148
+ self.resolution = resolution
149
+
150
+ assert self.head_dim * num_heads == dim, "dim must be divisible by num_heads"
151
+
152
+ key_qkv, key_proj, key_posemb = jr.split(key, 3)
153
+ self.qkv = eqx.nn.Linear(dim, dim * 3, use_bias=qkv_bias, key=key_qkv)
154
+ self.proj = eqx.nn.Linear(dim, dim, use_bias=proj_bias, key=key_proj)
155
+
156
+ self.pos_emb_funct = PosEmbMLPSwinv2D(
157
+ window_size=[resolution, resolution],
158
+ pretrained_window_size=[resolution, resolution],
159
+ num_heads=num_heads,
160
+ seq_len=seq_len,
161
+ key=key_posemb,
162
+ )
163
+
164
+ self.q_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
165
+ self.k_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
166
+
167
+ self.attn_drop = Dropout(attn_drop)
168
+ self.proj_drop = Dropout(proj_drop)
169
+
170
+ def __call__(
171
+ self,
172
+ x: Float[Array, "seqlen dim"],
173
+ enable_dropout: bool,
174
+ key: PRNGKeyArray,
175
+ ) -> Float[Array, "seqlen dim"]:
176
+ key1, key2 = jr.split(key, 2)
177
+
178
+ qkv = jax.vmap(self.qkv)(x)
179
+ qkv = rearrange(
180
+ qkv,
181
+ "s (n h d) -> n h s d",
182
+ n=3,
183
+ h=self.num_heads,
184
+ d=self.dim // self.num_heads,
185
+ )
186
+ q, k, v = qkv
187
+ q = jax.vmap(jax.vmap(self.q_norm))(q)
188
+ k = jax.vmap(jax.vmap(self.k_norm))(k)
189
+
190
+ attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
191
+ attn = self.pos_emb_funct(attn, self.resolution**2)
192
+ attn = jax.nn.softmax(attn, axis=-1)
193
+ attn = self.attn_drop(attn, inference=not enable_dropout, key=key1)
194
+
195
+ x = jnp.einsum("hqk,hvd->hqd", attn, v)
196
+ x = rearrange(x, "h s d -> s (h d)")
197
+ x = jax.vmap(self.proj)(x)
198
+ x = self.proj_drop(x, inference=not enable_dropout, key=key2)
199
+
200
+ return x
201
+
202
+
203
+ class AttentionBlock(eqx.Module):
204
+ """Standard transformer block with attention and MLP.
205
+
206
+ Implements a full transformer block with:
207
+ - Multi-head self attention
208
+ - Layer normalization
209
+ - MLP feed-forward network
210
+ - Residual connections
211
+ - Optional layer scaling
212
+ - Dropout paths
213
+
214
+ Attributes:
215
+ norm1: First layer normalization
216
+ norm2: Second layer normalization
217
+ ls1: First layer scale (optional)
218
+ ls2: Second layer scale (optional)
219
+ """
220
+
221
+ norm1: eqx.Module
222
+ norm2: eqx.Module
223
+ ls1: LayerScale
224
+ ls2: LayerScale
225
+ attn: eqx.Module
226
+ mlp: eqx.Module
227
+ drop_path1: DropPathAdd
228
+ drop_path2: DropPathAdd
229
+
230
+ def __init__(
231
+ self,
232
+ dim: int,
233
+ num_heads: int,
234
+ *,
235
+ key: PRNGKeyArray,
236
+ mlp_ratio: float = 4.0,
237
+ drop_path: float | List[float] = 0.0,
238
+ qkv_bias: bool = True,
239
+ proj_bias: bool = True,
240
+ qk_norm: bool = False,
241
+ attn_drop: float = 0.0,
242
+ proj_drop: float = 0.0,
243
+ act_layer: Callable = jax.nn.gelu,
244
+ attn_layer: eqx.Module = Attention,
245
+ ffn_layer: eqx.Module = Mlp,
246
+ ffn_bias: bool = True,
247
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
248
+ init_values: float | None = None,
249
+ **kwargs,
250
+ ):
251
+ key_attn, key_mlp = jr.split(key, 2)
252
+
253
+ if isinstance(drop_path, list):
254
+ if len(drop_path) != 2:
255
+ raise AssertionError(
256
+ f"`drop_path` needs to have 2 elements, got {len(drop_path)} ({drop_path})."
257
+ )
258
+ dr1, dr2 = drop_path
259
+ dr1 = float(dr1)
260
+ dr2 = float(dr2)
261
+ else:
262
+ dr1 = dr2 = float(drop_path)
263
+
264
+ self.norm1 = norm_layer(dim)
265
+ self.norm2 = norm_layer(dim)
266
+
267
+ if init_values:
268
+ self.ls1 = LayerScale(dim, init_values=init_values)
269
+ self.ls2 = LayerScale(dim, init_values=init_values)
270
+ else:
271
+ self.ls1 = self.ls2 = eqx.nn.Identity()
272
+
273
+ self.attn = attn_layer(
274
+ dim=dim,
275
+ num_heads=num_heads,
276
+ qkv_bias=qkv_bias,
277
+ proj_bias=proj_bias,
278
+ qk_norm=qk_norm,
279
+ attn_drop=attn_drop,
280
+ proj_drop=proj_drop,
281
+ norm_layer=norm_layer,
282
+ key=key_attn,
283
+ )
284
+
285
+ self.mlp = ffn_layer(
286
+ in_features=dim,
287
+ hidden_features=int(dim * mlp_ratio),
288
+ act_layer=act_layer,
289
+ dropout_rate=proj_drop,
290
+ bias=ffn_bias,
291
+ key=key_mlp,
292
+ )
293
+
294
+ self.drop_path1 = DropPathAdd(dr1)
295
+ self.drop_path2 = DropPathAdd(dr2)
296
+
297
+ def __call__(
298
+ self,
299
+ x: Float[Array, "seqlen dim"],
300
+ enable_dropout: bool,
301
+ key: PRNGKeyArray,
302
+ ) -> Float[Array, "seqlen dim"]:
303
+ key_attn, key_mlp, key_dr1, key_dr2 = jr.split(key, 4)
304
+
305
+ x = self.drop_path1(
306
+ x,
307
+ self.ls1(
308
+ self.attn(
309
+ jax.vmap(self.norm1)(x),
310
+ enable_dropout,
311
+ key=key_attn,
312
+ )
313
+ ),
314
+ inference=not enable_dropout,
315
+ key=key_dr1,
316
+ )
317
+ x = self.drop_path2(
318
+ x,
319
+ self.ls2(
320
+ self.mlp(
321
+ jax.vmap(self.norm2)(x),
322
+ enable_dropout,
323
+ key=key_mlp,
324
+ )
325
+ ),
326
+ inference=not enable_dropout,
327
+ key=key_dr2,
328
+ )
329
+
330
+ return x
331
+
332
+
333
+ class HATBlock(eqx.Module):
334
+ """Hierarchical Attention Transformer block.
335
+
336
+ Implements hierarchical attention with:
337
+ - Local window attention
338
+ - Carrier tokens for global interaction
339
+ - Optional token propagation in final layer
340
+ - Hierarchical structure with different spatial resolutions
341
+
342
+ Attributes:
343
+ do_propagation: Whether to propagate carrier token info in last layer
344
+ sr_ratio: Spatial reduction ratio for hierarchical structure
345
+ window_size: Size of local attention windows
346
+ ct_size: Size of carrier token grid
347
+ last: Whether this is the last block in the network
348
+ """
349
+
350
+ do_propagation: bool = eqx.field(static=True)
351
+ sr_ratio: float = eqx.field(static=True)
352
+ window_size: int = eqx.field(static=True)
353
+ ct_size: int = eqx.field(static=True)
354
+ last: bool = eqx.field(static=True)
355
+
356
+ norm1: eqx.Module
357
+ norm2: eqx.Module
358
+ ls1: LayerScale
359
+ ls2: LayerScale
360
+ attn: eqx.Module
361
+ mlp: eqx.Module
362
+ drop_path1: DropPathAdd
363
+ drop_path2: DropPathAdd
364
+ pos_embed: PosEmbMLPSwinv1D
365
+
366
+ hat_norm1: Optional[eqx.Module] = eqx.field(default=None)
367
+ hat_norm2: Optional[eqx.Module] = eqx.field(default=None)
368
+ hat_norm3: Optional[eqx.Module] = eqx.field(default=None)
369
+ hat_attn: Optional[eqx.Module] = eqx.field(default=None)
370
+ hat_mlp: Optional[eqx.Module] = eqx.field(default=None)
371
+ hat_drop_path: Optional[DropPathAdd] = eqx.field(default=None)
372
+ hat_pos_embed: Optional[PosEmbMLPSwinv1D] = eqx.field(default=None)
373
+ hat_ls1: Optional[LayerScale] = eqx.field(default=None)
374
+ hat_ls2: Optional[LayerScale] = eqx.field(default=None)
375
+ hat_ls3: Optional[LayerScale] = eqx.field(default=None)
376
+
377
+ def __init__(
378
+ self,
379
+ dim: int,
380
+ num_heads: int,
381
+ window_size: int,
382
+ *,
383
+ key: PRNGKeyArray,
384
+ mlp_ratio: float = 4.0,
385
+ drop_path: float | List[float] = 0.0,
386
+ qkv_bias: bool = True,
387
+ proj_bias: bool = True,
388
+ qk_norm: bool = False,
389
+ attn_drop: float = 0.0,
390
+ proj_drop: float = 0.0,
391
+ act_layer: Callable = jax.nn.gelu,
392
+ attn_layer: eqx.Module = Attention,
393
+ ffn_layer: eqx.Module = Mlp,
394
+ ffn_bias: bool = True,
395
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
396
+ init_values: float | None = None,
397
+ sr_ratio: float = 1.0,
398
+ ct_size: int = 1,
399
+ last: bool = False,
400
+ do_propagation: bool = False,
401
+ **kwargs,
402
+ ):
403
+ key_posemb, key_hatposemb, key_attn, key_hatattn, key_mlp, key_hatmlp = (
404
+ jr.split(key, 6)
405
+ )
406
+ self.do_propagation = do_propagation
407
+ self.sr_ratio = sr_ratio
408
+ self.window_size = window_size
409
+ self.ct_size = ct_size
410
+ self.last = last
411
+
412
+ if isinstance(drop_path, list):
413
+ if len(drop_path) != 2:
414
+ raise AssertionError(
415
+ f"`drop_path` needs to have 2 elements, got {len(drop_path)} ({drop_path})."
416
+ )
417
+ dr1, dr2 = drop_path
418
+ dr1 = float(dr1)
419
+ dr2 = float(dr2)
420
+ else:
421
+ dr1 = dr2 = float(drop_path)
422
+
423
+ self.pos_embed = PosEmbMLPSwinv1D(
424
+ dim, rank=2, seq_len=window_size**2, key=key_posemb
425
+ )
426
+ self.norm1 = norm_layer(dim)
427
+ self.norm2 = norm_layer(dim)
428
+
429
+ if init_values:
430
+ self.ls1 = LayerScale(dim, init_values=init_values)
431
+ self.ls2 = LayerScale(dim, init_values=init_values)
432
+ else:
433
+ self.ls1 = self.ls2 = eqx.nn.Identity()
434
+
435
+ # number of carrier tokens per every window
436
+ cr_tokens_per_window = self.ct_size**2 if self.sr_ratio > 1 else 0
437
+ cr_tokens_total = cr_tokens_per_window * self.sr_ratio * self.sr_ratio
438
+
439
+ self.attn = WindowedAttention(
440
+ resolution=self.window_size,
441
+ seq_len=self.window_size**2 + cr_tokens_per_window,
442
+ dim=dim,
443
+ num_heads=num_heads,
444
+ qkv_bias=qkv_bias,
445
+ proj_bias=proj_bias,
446
+ qk_norm=qk_norm,
447
+ attn_drop=attn_drop,
448
+ proj_drop=proj_drop,
449
+ norm_layer=norm_layer,
450
+ key=key_attn,
451
+ )
452
+
453
+ self.mlp = ffn_layer(
454
+ in_features=dim,
455
+ hidden_features=int(dim * mlp_ratio),
456
+ act_layer=act_layer,
457
+ dropout_rate=proj_drop,
458
+ bias=ffn_bias,
459
+ key=key_mlp,
460
+ )
461
+
462
+ self.drop_path1 = DropPathAdd(dr1)
463
+ self.drop_path2 = DropPathAdd(dr2)
464
+
465
+ if self.sr_ratio > 1:
466
+ # If hierarchical attention, this part is for carrier tokens
467
+ self.hat_norm1 = norm_layer(dim)
468
+ self.hat_norm2 = norm_layer(dim)
469
+
470
+ self.hat_attn = WindowedAttention(
471
+ resolution=int(cr_tokens_total**0.5),
472
+ seq_len=cr_tokens_total,
473
+ dim=dim,
474
+ num_heads=num_heads,
475
+ qkv_bias=qkv_bias,
476
+ proj_bias=proj_bias,
477
+ qk_norm=qk_norm,
478
+ attn_drop=attn_drop,
479
+ proj_drop=proj_drop,
480
+ norm_layer=norm_layer,
481
+ key=key_hatattn,
482
+ )
483
+
484
+ self.hat_mlp = ffn_layer(
485
+ in_features=dim,
486
+ hidden_features=int(dim * mlp_ratio),
487
+ act_layer=act_layer,
488
+ dropout_rate=proj_drop,
489
+ bias=ffn_bias,
490
+ key=key_hatmlp,
491
+ )
492
+
493
+ self.hat_drop_path = DropPathAdd(dr2)
494
+
495
+ self.hat_pos_embed = PosEmbMLPSwinv1D(
496
+ dim, rank=2, seq_len=cr_tokens_total, key=key_hatposemb
497
+ )
498
+ self.hat_ls1 = (
499
+ LayerScale(dim, init_values=init_values)
500
+ if init_values
501
+ else eqx.nn.Identity()
502
+ )
503
+ self.hat_ls2 = (
504
+ LayerScale(dim, init_values=init_values)
505
+ if init_values
506
+ else eqx.nn.Identity()
507
+ )
508
+ self.hat_ls3 = (
509
+ LayerScale(dim, init_values=init_values)
510
+ if init_values
511
+ else eqx.nn.Identity()
512
+ )
513
+
514
+ def ct_window(self, ct, H, W, window_size):
515
+ return rearrange(
516
+ ct,
517
+ "(h h1 w w1) d -> (h w h1 w1) d",
518
+ h=H,
519
+ w=W,
520
+ h1=window_size,
521
+ w1=window_size,
522
+ )
523
+
524
+ def ct_dewindow(self, ct, H, W, window_size):
525
+ return rearrange(
526
+ ct,
527
+ "(h w h1 w1) d -> (h h1 w w1) d",
528
+ h=H,
529
+ w=W,
530
+ h1=window_size,
531
+ w1=window_size,
532
+ )
533
+
534
+ def __call__(
535
+ self,
536
+ x: Float[Array, "seqlen dim"],
537
+ carrier_tokens: Float[Array, "..."],
538
+ enable_dropout: bool,
539
+ key: PRNGKeyArray,
540
+ ) -> Float[Array, "seqlen dim"]:
541
+ key_attn, key_hattn, key_dr1, key_hdr1, key_dr2, key_hdr2, key_mlp, key_hmlp = (
542
+ jr.split(key, 8)
543
+ )
544
+
545
+ s, n = x.shape
546
+ x = self.pos_embed(x)
547
+ ct = carrier_tokens
548
+
549
+ if self.sr_ratio > 1:
550
+ # do hierarchical attention via carrier tokens
551
+ # first do attention for carrier tokens
552
+ ng, hg = ct.shape
553
+
554
+ # ct are located quite differently
555
+ ct = self.ct_dewindow(
556
+ ct,
557
+ self.sr_ratio,
558
+ self.sr_ratio,
559
+ self.ct_size,
560
+ )
561
+
562
+ # positional bias for carrier tokens
563
+ ct = self.hat_pos_embed(ct)
564
+
565
+ # attention plus mlp
566
+ ct = self.hat_drop_path(
567
+ ct,
568
+ self.hat_ls1(
569
+ self.hat_attn(
570
+ jax.vmap(self.hat_norm1)(ct),
571
+ enable_dropout,
572
+ key_hattn,
573
+ )
574
+ ),
575
+ inference=not enable_dropout,
576
+ key=key_hdr1,
577
+ )
578
+ ct = self.hat_drop_path(
579
+ ct,
580
+ self.hat_ls2(
581
+ self.hat_mlp(
582
+ jax.vmap(self.hat_norm2)(ct),
583
+ enable_dropout,
584
+ key_hmlp,
585
+ )
586
+ ),
587
+ inference=not enable_dropout,
588
+ key=key_hdr2,
589
+ )
590
+
591
+ # ct are put back to windows
592
+ ct = self.ct_window(
593
+ ct,
594
+ self.sr_ratio,
595
+ self.sr_ratio,
596
+ self.ct_size,
597
+ )
598
+
599
+ # concatenate carrier_tokens to the windowed tokens
600
+ x = jnp.concatenate((x, ct), axis=0)
601
+
602
+ x = self.drop_path1(
603
+ x,
604
+ self.ls1(
605
+ self.attn(
606
+ jax.vmap(self.norm1)(x),
607
+ enable_dropout,
608
+ key_attn,
609
+ )
610
+ ),
611
+ inference=not enable_dropout,
612
+ key=key_dr1,
613
+ )
614
+ x = self.drop_path2(
615
+ x,
616
+ self.ls2(
617
+ self.mlp(
618
+ jax.vmap(self.norm2)(x),
619
+ enable_dropout,
620
+ key_mlp,
621
+ )
622
+ ),
623
+ inference=not enable_dropout,
624
+ key=key_dr2,
625
+ )
626
+
627
+ if self.sr_ratio > 1:
628
+ # for hierarchical attention we need to split carrier tokens and window tokens back
629
+ split_index = self.window_size * self.window_size
630
+ x, ctr = jnp.split(x, [split_index], axis=0)
631
+
632
+ if self.last and self.do_propagation:
633
+ # propagate carrier token information into the image
634
+ ctr_image_space = rearrange(
635
+ ctr,
636
+ "(h w) c -> c h w",
637
+ h=self.ct_size * self.sr_ratio,
638
+ w=self.ct_size * self.sr_ratio,
639
+ )
640
+ upsampled = jax.image.resize(
641
+ ctr_image_space,
642
+ (n, self.window_size, self.window_size),
643
+ method="nearest",
644
+ )
645
+ upsampled = rearrange(upsampled, "c h w -> (h w) c")
646
+
647
+ x = x + self.hat_ls3(upsampled)
648
+
649
+ return x, ct
650
+
651
+
652
+ class SHSA(eqx.Module):
653
+ """Signe-Head Self Attention module from the SHViT paper.
654
+
655
+ "In SHSA, self-attention with a single head is applied to just a subset of
656
+ the input channels, while the others remain unchanged. SHSA layer not only
657
+ eliminates the computational redundancy derived from multi-head mechanism
658
+ but also reduces memory access cost by processing partial channels"
659
+
660
+ Attributes:
661
+ scale: Scaling factor for attention scores
662
+ qk_dim: Dimension of query/key projections
663
+ pdim: Dimension of primary features
664
+ """
665
+
666
+ scale: eqx.field(static=True)
667
+ qk_dim: eqx.field(static=True)
668
+ pdim: eqx.field(static=True)
669
+
670
+ pre_norm: eqx.Module
671
+ qkv: eqx.Module
672
+ proj: eqx.Module
673
+
674
+ def __init__(
675
+ self,
676
+ dim: int,
677
+ qk_dim: int,
678
+ pdim: int,
679
+ *,
680
+ key: PRNGKeyArray,
681
+ norm_max_group: int = 32,
682
+ **kwargs,
683
+ ):
684
+ key_conv1, key_conv2 = jr.split(key, 2)
685
+
686
+ self.scale = qk_dim**-0.5
687
+ self.qk_dim = qk_dim
688
+ self.pdim = pdim
689
+
690
+ self.pre_norm = eqx.nn.GroupNorm(1, pdim)
691
+ self.qkv = eqx.nn.Conv(
692
+ num_spatial_dims=2,
693
+ in_channels=pdim,
694
+ out_channels=qk_dim * 2 + pdim,
695
+ kernel_size=1,
696
+ stride=1,
697
+ padding=0,
698
+ key=key_conv1,
699
+ )
700
+ num_groups = nearest_power_of_2_divisor(dim, norm_max_group)
701
+ self.proj = eqx.nn.Sequential(
702
+ [
703
+ eqx.nn.Lambda(jax.nn.relu),
704
+ eqx.nn.Conv(
705
+ num_spatial_dims=2,
706
+ in_channels=dim,
707
+ out_channels=dim,
708
+ kernel_size=1,
709
+ stride=1,
710
+ padding=0,
711
+ key=key_conv2,
712
+ ),
713
+ eqx.nn.GroupNorm(num_groups, dim),
714
+ ]
715
+ )
716
+
717
+ def flatten(self, x: Float[Array, "channels height width"]):
718
+ return rearrange(x, "c h w -> c (h w)")
719
+
720
+ def __call__(
721
+ self,
722
+ x: Float[Array, "channels height width"],
723
+ enable_dropout: Optional[bool] = None,
724
+ key: Optional[PRNGKeyArray] = None,
725
+ ) -> Float[Array, "channels height width"]:
726
+ C, H, W = x.shape
727
+ x1, x2 = jnp.split(x, [self.pdim], axis=0)
728
+ x1 = self.pre_norm(x1)
729
+ qkv = self.qkv(x1)
730
+ q, k, v = jnp.split(qkv, [self.qk_dim, self.qk_dim * 2], axis=0)
731
+ q, k, v = self.flatten(q), self.flatten(k), self.flatten(v)
732
+
733
+ attn = jnp.einsum("dq,dk->qk", q, k) * self.scale
734
+ attn = jax.nn.softmax(attn, axis=-1)
735
+
736
+ x1 = jnp.einsum("qk,dv->qd", attn, v)
737
+ x1 = rearrange(x1, "(h w) d -> d h w", h=H, w=W, d=self.pdim)
738
+
739
+ x = jnp.concat([x1, x2], axis=0)
740
+ x = self.proj(x)
741
+
742
+ return x
743
+
744
+
745
+ class LinearAttention(eqx.Module):
746
+ """Linear attention with rotary position encoding.
747
+
748
+ Implements efficient linear attention with:
749
+ - Linear complexity in sequence length
750
+ - Rotary position embeddings
751
+ - Learnable position encoding (LePE)
752
+ - Multi-head structure
753
+
754
+ Attributes:
755
+ num_heads: Number of attention heads
756
+ """
757
+
758
+ num_heads: int = eqx.field(static=True)
759
+
760
+ qk: eqx.Module
761
+ lepe: eqx.Module
762
+ rope: eqx.Module
763
+
764
+ def __init__(
765
+ self,
766
+ input_resolution: Tuple[int, int],
767
+ dim: int,
768
+ num_heads: int,
769
+ *,
770
+ key: PRNGKeyArray,
771
+ **kwargs,
772
+ ):
773
+ key_fc1, key_conv = jr.split(key, 2)
774
+ self.num_heads = num_heads
775
+
776
+ self.qk = eqx.nn.Linear(dim, dim * 2, key=key_fc1)
777
+ self.lepe = eqx.nn.Conv(
778
+ num_spatial_dims=2,
779
+ in_channels=dim,
780
+ out_channels=dim,
781
+ kernel_size=3,
782
+ stride=1,
783
+ padding=1,
784
+ groups=dim,
785
+ key=key_conv,
786
+ )
787
+ self.rope = RoPE((input_resolution[0], input_resolution[1], dim))
788
+
789
+ def __call__(
790
+ self,
791
+ x: Float[Array, "seqlen dim"],
792
+ enable_dropout: Optional[bool] = None,
793
+ key: Optional[PRNGKeyArray] = None,
794
+ ) -> Float[Array, "seqlen dim"]:
795
+ n, c = x.shape
796
+ h = w = int(n**0.5)
797
+
798
+ q, k = rearrange(
799
+ jax.vmap(self.qk)(x),
800
+ "n (qk h d) -> qk h n d",
801
+ qk=2,
802
+ h=self.num_heads,
803
+ )
804
+ v = rearrange(x, "n (h d) -> h n d", h=self.num_heads)
805
+
806
+ q = jax.nn.elu(q) + 1.0
807
+ k = jax.nn.elu(k) + 1.0
808
+
809
+ q_2d = rearrange(q, "h (x y) d -> x y (h d)", x=h, y=w)
810
+ k_2d = rearrange(k, "h (x y) d -> x y (h d)", x=h, y=w)
811
+
812
+ q_rope = rearrange(self.rope(q_2d), "x y (h d) -> h (x y) d", h=self.num_heads)
813
+ k_rope = rearrange(self.rope(k_2d), "x y (h d) -> h (x y) d", h=self.num_heads)
814
+
815
+ # Compute attention
816
+ z = 1 / (jnp.einsum("hnd,hd->hn", q, reduce(k, "h n d -> h d", "mean")) + 1e-6)
817
+ kv = jnp.einsum("hnd,hne->hde", k_rope * (n**-0.5), v * (n**-0.5))
818
+ x = jnp.einsum("hnd,hde->hne", q_rope, kv) * z[..., None]
819
+
820
+ # Reshape output
821
+ x = rearrange(x, "h n d -> n (h d)")
822
+
823
+ # Apply LePE
824
+ v_2d = rearrange(v, "h (x y) d -> (h d) x y", x=h, y=w)
825
+ lepe_out = self.lepe(v_2d)
826
+
827
+ lepe_out = rearrange(lepe_out, "(h d) x y -> (x y) (h d)", h=self.num_heads)
828
+
829
+ # Combine attention output and LePE
830
+ x = x + lepe_out
831
+
832
+ return x
833
+
834
+
835
+ class MllaBlock(eqx.Module):
836
+ """Mamba-like Linear Attention block.
837
+
838
+ Implements a transformer block using linear attention that:
839
+ - Uses convolutional position encoding
840
+ - Optionally includes depthwise convolution
841
+ - Has residual connections and layer norms
842
+ - Includes MLP feed-forward network
843
+
844
+ Attributes:
845
+ use_dwc: Whether to use depthwise convolution
846
+ """
847
+
848
+ use_dwc: bool = eqx.field(static=True)
849
+
850
+ act: Callable
851
+ cpe1: eqx.Module
852
+ cpe2: eqx.Module
853
+ norm1: eqx.Module
854
+ norm2: eqx.Module
855
+ in_proj: eqx.Module
856
+ act_proj: eqx.Module
857
+ out_proj: eqx.Module
858
+ dwc: eqx.Module
859
+ attn: eqx.Module
860
+ drop_path1: eqx.Module
861
+ drop_path2: eqx.Module
862
+ mlp: eqx.Module
863
+
864
+ def __init__(
865
+ self,
866
+ dim: int,
867
+ *,
868
+ key: PRNGKeyArray,
869
+ act_layer: Callable = jax.nn.silu, # gelu in VSSD
870
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
871
+ use_dwc: bool = True, # For Mlla but not VMamba-2
872
+ attention_layer: eqx.Module = LinearAttention,
873
+ drop_path: List[float] | float = 0.0,
874
+ mlp_ratio: float = 4.0,
875
+ ffn_layer: eqx.Module = Mlp,
876
+ ffn_bias: bool = True,
877
+ proj_drop: float = 0.0,
878
+ **kwargs,
879
+ ):
880
+ if attention_layer not in [Attention, LinearAttention, Mamba2Mixer]:
881
+ raise ValueError(
882
+ "Unsupported `attention_layer`, got:",
883
+ attention_layer,
884
+ )
885
+
886
+ (
887
+ key_conv1,
888
+ key_conv2,
889
+ key_conv3,
890
+ key_fc1,
891
+ key_fc2,
892
+ key_fc3,
893
+ key_attn,
894
+ key_mlp,
895
+ ) = jr.split(key, 8)
896
+ self.use_dwc = use_dwc
897
+ self.act = act_layer
898
+
899
+ self.cpe1 = eqx.nn.Conv(
900
+ num_spatial_dims=2,
901
+ in_channels=dim,
902
+ out_channels=dim,
903
+ kernel_size=3,
904
+ stride=1,
905
+ padding=1,
906
+ groups=dim,
907
+ use_bias=True,
908
+ key=key_conv1,
909
+ )
910
+ self.norm1 = norm_layer(dim)
911
+
912
+ if use_dwc:
913
+ self.in_proj = eqx.nn.Linear(dim, dim, key=key_fc1)
914
+ self.act_proj = eqx.nn.Linear(dim, dim, key=key_fc2)
915
+ self.dwc = eqx.nn.Conv(
916
+ num_spatial_dims=2,
917
+ in_channels=dim,
918
+ out_channels=dim,
919
+ kernel_size=3,
920
+ stride=1,
921
+ padding=1,
922
+ groups=dim,
923
+ use_bias=True,
924
+ key=key_conv2,
925
+ )
926
+ self.out_proj = eqx.nn.Linear(dim, dim, key=key_fc3)
927
+ else:
928
+ self.in_proj = self.act_proj = self.dwc = self.out_proj = eqx.nn.Identity()
929
+
930
+ config = {"d_model": dim} if attention_layer is Mamba2Mixer else {"dim": dim}
931
+
932
+ self.attn = attention_layer(
933
+ **(kwargs | config),
934
+ key=key_attn,
935
+ )
936
+
937
+ if isinstance(drop_path, list):
938
+ if len(drop_path) != 2:
939
+ raise AssertionError(
940
+ f"`drop_path` needs to have 2 elements, got {len(drop_path)} ({drop_path})."
941
+ )
942
+ dr1, dr2 = drop_path
943
+ dr1 = float(dr1)
944
+ dr2 = float(dr2)
945
+ else:
946
+ dr1 = dr2 = float(drop_path)
947
+
948
+ self.drop_path1 = DropPathAdd(dr1)
949
+
950
+ self.cpe2 = eqx.nn.Conv(
951
+ num_spatial_dims=2,
952
+ in_channels=dim,
953
+ out_channels=dim,
954
+ kernel_size=3,
955
+ stride=1,
956
+ padding=1,
957
+ groups=dim,
958
+ use_bias=True,
959
+ key=key_conv3,
960
+ )
961
+
962
+ self.norm2 = norm_layer(dim)
963
+ self.mlp = ffn_layer(
964
+ in_features=dim,
965
+ hidden_features=int(dim * mlp_ratio),
966
+ act_layer=act_layer,
967
+ dropout_rate=proj_drop,
968
+ bias=ffn_bias,
969
+ key=key_mlp,
970
+ )
971
+
972
+ self.drop_path2 = DropPathAdd(dr2)
973
+
974
+ def __call__(
975
+ self,
976
+ x: Float[Array, "seqlen dim"],
977
+ enable_dropout: Optional[bool] = None,
978
+ key: Optional[PRNGKeyArray] = None,
979
+ ) -> Float[Array, "seqlen dim"]:
980
+ key_attn, key_dr1, key_dr2, key_mlp = jr.split(key, 4)
981
+ l, _ = x.shape
982
+ h = w = int(l**0.5)
983
+
984
+ x1 = x + rearrange(
985
+ self.cpe1(rearrange(x, "(h w) c -> c h w", h=h, w=w)),
986
+ "c h w -> (h w) c",
987
+ )
988
+ x1 = jax.vmap(self.norm1)(x1)
989
+
990
+ if self.use_dwc:
991
+ act_res = self.act(jax.vmap(self.act_proj)(x1))
992
+
993
+ x1 = rearrange(jax.vmap(self.in_proj)(x1), "(h w) c -> c h w", h=h, w=w)
994
+ x1 = self.act(rearrange(self.dwc(x1), "c h w -> (h w) c"))
995
+
996
+ x1 = self.attn(x1, enable_dropout, key_attn)
997
+
998
+ if self.use_dwc:
999
+ x1 = jax.vmap(self.out_proj)(x * act_res)
1000
+
1001
+ x = self.drop_path1(x, x1, inference=not enable_dropout, key=key_dr1)
1002
+
1003
+ x += rearrange(
1004
+ self.cpe2(rearrange(x, "(h w) c -> c h w", h=h, w=w)),
1005
+ "c h w -> (h w) c",
1006
+ )
1007
+
1008
+ return self.drop_path2(
1009
+ x,
1010
+ self.mlp(jax.vmap(self.norm2)(x), enable_dropout, key_mlp),
1011
+ inference=not enable_dropout,
1012
+ key=key_dr2,
1013
+ )
1014
+
1015
+
1016
+ class MMSA(eqx.Module):
1017
+ """Mixed Multi-head Self Attention.
1018
+
1019
+ Implements attention with:
1020
+ - Multi-head structure
1021
+ - Attention score projection for multi-scale interaction
1022
+ - Normalized query/key processing
1023
+ - Dropout regularization
1024
+
1025
+ Attributes:
1026
+ dim: Total dimension of input/output
1027
+ num_heads: Number of attention heads
1028
+ head_dim: Dimension per attention head
1029
+ """
1030
+
1031
+ dim: int = eqx.field(static=True)
1032
+ num_heads: int = eqx.field(static=True)
1033
+ head_dim: int = eqx.field(static=True)
1034
+
1035
+ qkv: eqx.nn.Linear
1036
+ proj: eqx.nn.Linear
1037
+ attn_proj1: eqx.nn.Linear
1038
+ attn_proj2: eqx.nn.Linear
1039
+ q_norm: eqx.Module
1040
+ k_norm: eqx.Module
1041
+ attn_drop: Dropout
1042
+ proj_drop: Dropout
1043
+
1044
+ def __init__(
1045
+ self,
1046
+ dim: int,
1047
+ num_heads: int,
1048
+ *,
1049
+ key: PRNGKeyArray,
1050
+ head_expand_ratio: float = 4.0,
1051
+ qkv_bias: bool = True,
1052
+ proj_bias: bool = True,
1053
+ qk_norm: bool = False,
1054
+ attn_drop: float = 0.0,
1055
+ proj_drop: float = 0.0,
1056
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
1057
+ **kwargs,
1058
+ ):
1059
+ self.num_heads = num_heads
1060
+ self.head_dim = dim // num_heads
1061
+ self.dim = dim
1062
+
1063
+ assert self.head_dim * num_heads == dim, "dim must be divisible by num_heads"
1064
+
1065
+ key_qkv, key_proj, key_attnproj1, key_attnproj2 = jr.split(key, 4)
1066
+ self.qkv = eqx.nn.Linear(dim, dim * 3, use_bias=qkv_bias, key=key_qkv)
1067
+ self.proj = eqx.nn.Linear(dim, dim, use_bias=proj_bias, key=key_proj)
1068
+ self.attn_proj1 = eqx.nn.Linear(
1069
+ num_heads,
1070
+ int(num_heads * head_expand_ratio),
1071
+ key=key_attnproj1,
1072
+ )
1073
+ self.attn_proj2 = eqx.nn.Linear(
1074
+ int(num_heads * head_expand_ratio),
1075
+ num_heads,
1076
+ key=key_attnproj2,
1077
+ )
1078
+
1079
+ self.q_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
1080
+ self.k_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
1081
+
1082
+ self.attn_drop = Dropout(attn_drop)
1083
+ self.proj_drop = Dropout(proj_drop)
1084
+
1085
+ def __call__(
1086
+ self,
1087
+ x: Float[Array, "seqlen dim"],
1088
+ enable_dropout: bool,
1089
+ key: PRNGKeyArray,
1090
+ ) -> Float[Array, "seqlen dim"]:
1091
+ key1, key2 = jr.split(key, 2)
1092
+
1093
+ qkv = jax.vmap(self.qkv)(x)
1094
+ qkv = rearrange(
1095
+ qkv,
1096
+ "s (n h d) -> n h s d",
1097
+ n=3,
1098
+ h=self.num_heads,
1099
+ d=self.dim // self.num_heads,
1100
+ )
1101
+ q, k, v = qkv
1102
+ q = jax.vmap(jax.vmap(self.q_norm))(q)
1103
+ k = jax.vmap(jax.vmap(self.k_norm))(k)
1104
+
1105
+ attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
1106
+ attn = jax.vmap(jax.vmap(self.attn_proj1))(
1107
+ rearrange(attn, "h q k -> q k h"),
1108
+ )
1109
+ attn = jax.nn.softmax(attn, axis=1)
1110
+ attn = rearrange(
1111
+ jax.vmap(jax.vmap(self.attn_proj2))(attn),
1112
+ "q k h -> h q k",
1113
+ )
1114
+ attn = self.attn_drop(attn, inference=not enable_dropout, key=key1)
1115
+
1116
+ x = jnp.einsum("hqk,hvd->hqd", attn, v)
1117
+ x = rearrange(x, "h s d -> s (h d)")
1118
+ x = jax.vmap(self.proj)(x)
1119
+ x = self.proj_drop(x, inference=not enable_dropout, key=key2)
1120
+
1121
+ return x
1122
+
1123
+
1124
+ class SQA(eqx.Module):
1125
+ """Single Query Attention module.
1126
+
1127
+ Implements efficient attention where:
1128
+ - Only one query is used against many keys/values
1129
+ - Includes projection layers for dimension reduction
1130
+ - Uses normalized attention computation
1131
+ - Includes residual connection
1132
+
1133
+ Attributes:
1134
+ dim: Total dimension of input/output
1135
+ num_heads: Number of attention heads
1136
+ head_dim: Dimension per attention head
1137
+ """
1138
+
1139
+ dim: int = eqx.field(static=True)
1140
+ num_heads: int = eqx.field(static=True)
1141
+ head_dim: int = eqx.field(static=True)
1142
+
1143
+ kv: eqx.nn.Linear
1144
+ proj1: eqx.nn.Linear
1145
+ proj2: eqx.nn.Linear
1146
+ proj_norm: eqx.Module
1147
+ q_norm: eqx.Module
1148
+ k_norm: eqx.Module
1149
+ attn_drop: Dropout
1150
+ proj_drop: Dropout
1151
+
1152
+ def __init__(
1153
+ self,
1154
+ dim: int,
1155
+ num_heads: int,
1156
+ *,
1157
+ key: PRNGKeyArray,
1158
+ kv_bias: bool = True,
1159
+ proj_bias: bool = True,
1160
+ proj_ratio: float = 4.0,
1161
+ qk_norm: bool = False,
1162
+ attn_drop: float = 0.0,
1163
+ proj_drop: float = 0.0,
1164
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
1165
+ **kwargs,
1166
+ ):
1167
+ self.num_heads = num_heads
1168
+ self.head_dim = dim // num_heads
1169
+ self.dim = dim
1170
+
1171
+ assert self.head_dim * num_heads == dim, "dim must be divisible by num_heads"
1172
+
1173
+ key_kv, key_proj = jr.split(key, 2)
1174
+ self.kv = eqx.nn.Linear(dim, dim * 2, use_bias=kv_bias, key=key_kv)
1175
+ self.proj1 = eqx.nn.Linear(
1176
+ dim, int(dim // proj_ratio), use_bias=proj_bias, key=key_proj
1177
+ )
1178
+ self.proj2 = eqx.nn.Linear(
1179
+ int(dim // proj_ratio), dim, use_bias=proj_bias, key=key_proj
1180
+ )
1181
+ self.proj_norm = norm_layer(int(dim // proj_ratio))
1182
+
1183
+ self.q_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
1184
+ self.k_norm = norm_layer(dim) if qk_norm else eqx.nn.Identity()
1185
+
1186
+ self.attn_drop = Dropout(attn_drop)
1187
+ self.proj_drop = Dropout(proj_drop)
1188
+
1189
+ def __call__(
1190
+ self,
1191
+ x: Float[Array, "seqlen dim"],
1192
+ q: Float[Array, "1 dim"],
1193
+ enable_dropout: bool,
1194
+ key: PRNGKeyArray,
1195
+ ) -> Float[Array, "seqlen_x dim"]:
1196
+ key1, key2 = jr.split(key, 2)
1197
+
1198
+ q = rearrange(
1199
+ q,
1200
+ "1 (h d) -> h 1 d",
1201
+ h=self.num_heads,
1202
+ d=self.dim // self.num_heads,
1203
+ )
1204
+ kv = jax.vmap(self.kv)(x)
1205
+ kv = rearrange(
1206
+ kv,
1207
+ "s (n h d) -> n h s d",
1208
+ n=2,
1209
+ h=self.num_heads,
1210
+ d=self.dim // self.num_heads,
1211
+ )
1212
+ k, v = kv
1213
+ q = jax.vmap(jax.vmap(self.q_norm))(q)
1214
+ k = jax.vmap(jax.vmap(self.k_norm))(k)
1215
+
1216
+ attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
1217
+ attn = jax.nn.softmax(attn, axis=-1)
1218
+ attn = self.attn_drop(attn, inference=not enable_dropout, key=key1)
1219
+
1220
+ x1 = jnp.einsum("hqk,hvd->hqd", attn, v)
1221
+ x1 = rearrange(x1, "h s d -> s (h d)")
1222
+ x1 = jax.vmap(self.proj_norm)(jax.vmap(self.proj1)(x1))
1223
+ x1 = jax.vmap(self.proj2)(jax.nn.relu(x1))
1224
+
1225
+ x = self.proj_drop(x + x1, inference=not enable_dropout, key=key2)
1226
+
1227
+ return x
1228
+
1229
+
1230
+ class PartialFormerBlock(eqx.Module):
1231
+ """Partial Transformer block with foreground/background separation.
1232
+
1233
+ Implements a specialized transformer that:
1234
+ - Separates input into foreground/background regions
1235
+ - Uses different attention mechanisms for each region
1236
+ - Includes positional encoding and MLP layers
1237
+ - Has layer scaling and dropout paths
1238
+
1239
+ Attributes:
1240
+ foreground_ratio: Ratio of tokens treated as foreground
1241
+ patch_size: Size of input patches
1242
+ """
1243
+
1244
+ foreground_ratio: float = eqx.field(static=True)
1245
+ patch_size: float = eqx.field(static=True)
1246
+
1247
+ act: Callable
1248
+ posemb: eqx.Module
1249
+ norm1: eqx.Module
1250
+ norm2: eqx.Module
1251
+ mmsa: eqx.Module
1252
+ sqa: eqx.Module
1253
+ ls1: eqx.Module
1254
+ ls2: eqx.Module
1255
+ drop_path1: eqx.Module
1256
+ drop_path2: eqx.Module
1257
+ mlp: eqx.Module
1258
+
1259
+ def __init__(
1260
+ self,
1261
+ dim: int,
1262
+ num_heads: int,
1263
+ foreground_ratio: int,
1264
+ patch_size: int,
1265
+ *,
1266
+ key: PRNGKeyArray,
1267
+ head_expand_ratio: float = 4.0,
1268
+ qkv_bias: bool = True,
1269
+ proj_bias: bool = True,
1270
+ qk_norm: bool = False,
1271
+ attn_drop: float = 0.0,
1272
+ proj_drop: float = 0.0,
1273
+ proj_ratio: float = 4.0,
1274
+ act_layer: Callable = jax.nn.silu, # gelu in VSSD
1275
+ norm_layer: eqx.Module = eqx.nn.LayerNorm,
1276
+ drop_path: List[float] | float = 0.0,
1277
+ mlp_ratio: float = 4.0,
1278
+ ffn_layer: eqx.Module = Mlp,
1279
+ ffn_bias: bool = True,
1280
+ init_values: float | None = None,
1281
+ **kwargs,
1282
+ ):
1283
+ (
1284
+ key_posemb,
1285
+ key_mmsa,
1286
+ key_sqa,
1287
+ key_mlp,
1288
+ ) = jr.split(key, 4)
1289
+ self.act = act_layer
1290
+ self.foreground_ratio = foreground_ratio
1291
+ self.patch_size = patch_size
1292
+
1293
+ if isinstance(drop_path, list):
1294
+ if len(drop_path) != 2:
1295
+ raise AssertionError(
1296
+ f"`drop_path` needs to have 2 elements, got {len(drop_path)} ({drop_path})."
1297
+ )
1298
+ dr1, dr2 = drop_path
1299
+ dr1 = float(dr1)
1300
+ dr2 = float(dr2)
1301
+ else:
1302
+ dr1 = dr2 = float(drop_path)
1303
+
1304
+ self.norm1 = norm_layer(dim)
1305
+ self.norm2 = norm_layer(dim)
1306
+
1307
+ if init_values:
1308
+ self.ls1 = LayerScale(dim, init_values=init_values)
1309
+ self.ls2 = LayerScale(dim, init_values=init_values)
1310
+ else:
1311
+ self.ls1 = self.ls2 = eqx.nn.Identity()
1312
+
1313
+ self.posemb = PosCNN2D(dim, dim, key=key_posemb)
1314
+
1315
+ self.mmsa = MMSA(
1316
+ dim=dim,
1317
+ num_heads=num_heads,
1318
+ head_expand_ratio=head_expand_ratio,
1319
+ qkv_bias=qkv_bias,
1320
+ proj_bias=proj_bias,
1321
+ qk_norm=qk_norm,
1322
+ attn_drop=attn_drop,
1323
+ proj_drop=proj_drop,
1324
+ norm_layer=norm_layer,
1325
+ key=key_mmsa,
1326
+ )
1327
+ self.sqa = SQA(
1328
+ dim=dim,
1329
+ num_heads=num_heads,
1330
+ kv_bias=qkv_bias,
1331
+ qk_norm=qk_norm,
1332
+ attn_drop=attn_drop,
1333
+ proj_bias=proj_bias,
1334
+ proj_ratio=proj_ratio,
1335
+ proj_drop=proj_drop,
1336
+ norm_layer=norm_layer,
1337
+ key=key_sqa,
1338
+ )
1339
+
1340
+ self.mlp = ffn_layer(
1341
+ in_features=dim,
1342
+ hidden_features=int(dim * mlp_ratio),
1343
+ act_layer=act_layer,
1344
+ dropout_rate=proj_drop,
1345
+ bias=ffn_bias,
1346
+ key=key_mlp,
1347
+ )
1348
+
1349
+ self.drop_path1 = DropPathAdd(dr1)
1350
+ self.drop_path2 = DropPathAdd(dr2)
1351
+
1352
+ def __call__(
1353
+ self,
1354
+ x: Float[Array, "seqlen dim"],
1355
+ qa: Float[Array, "1 dim"],
1356
+ enable_dropout: Optional[bool] = None,
1357
+ key: Optional[PRNGKeyArray] = None,
1358
+ ) -> Tuple[Float[Array, "seqlen dim"], Float[Array, "1 dim"]]:
1359
+ key_mmsa, key_sqa, key_dr1, key_dr2, key_mlp = jr.split(key, 5)
1360
+ l, _ = x.shape
1361
+ h = w = int(l**0.5)
1362
+
1363
+ x1 = rearrange(x, "(h w) c -> c h w", h=h, w=w)
1364
+ x1 = self.posemb(x1)
1365
+ x1 = rearrange(
1366
+ x1,
1367
+ "c (h h1) (w w1) -> (h w) (h1 w1) c",
1368
+ h1=self.patch_size,
1369
+ w1=self.patch_size,
1370
+ )
1371
+
1372
+ n = x1.shape[0]
1373
+ nf = int(n * self.foreground_ratio)
1374
+
1375
+ idx = jnp.argsort(reduce(x1, "n s c -> n", "mean"), descending=True)
1376
+ nf_idx, nb_idx = jnp.split(idx, [nf])
1377
+
1378
+ f, b = x1[nf_idx], x1[nb_idx]
1379
+
1380
+ f = rearrange(f, "n s c -> (n s) c")
1381
+ b = rearrange(b, "n s c -> (n s) c")
1382
+
1383
+ qf = self.mmsa(jnp.concat([qa, f], axis=0), enable_dropout, key=key_mmsa)
1384
+ qa, f = jnp.split(qf, [1])
1385
+ b = self.sqa(b, qa, enable_dropout, key=key_sqa)
1386
+
1387
+ x1 = self.ls1(jnp.concat([f, b], axis=0))
1388
+ x = self.drop_path1(x, x1, inference=not enable_dropout, key=key_dr1)
1389
+
1390
+ qa, x1 = jnp.split(
1391
+ self.mlp(
1392
+ jax.vmap(self.norm2)(jnp.concat([qa, x])),
1393
+ enable_dropout,
1394
+ key=key_mlp,
1395
+ )[1],
1396
+ )
1397
+ x = self.drop_path2(
1398
+ x,
1399
+ self.ls2(x1),
1400
+ inference=not enable_dropout,
1401
+ key=key_dr2,
1402
+ )
1403
+
1404
+ return x, qa
1405
+
1406
+
1407
+ class LinearAngularAttention(eqx.Module):
1408
+ """Linear Angular Attention with optional sparsity.
1409
+
1410
+ This is the base attention module to be included in a ViT to replicate
1411
+ Castling-ViT[1].
1412
+ Implements an efficient attention variant that:
1413
+ - Uses normalized vectors for computing attention
1414
+ - Applies optional sparsity regularization
1415
+ - Includes depthwise convolution for local mixing
1416
+ - Has linear complexity in sequence length
1417
+
1418
+ Attributes:
1419
+ dim: Total dimension of input/output
1420
+ num_heads: Number of attention heads
1421
+ head_dim: Dimension per attention head
1422
+ sparse_reg: Whether to use sparsity regularization
1423
+ sparsity_threshold: Threshold for sparse attention
1424
+
1425
+ References:
1426
+ [1]: You, et al., 2024. https://arxiv.org/abs/2211.10526
1427
+ """
1428
+
1429
+ dim: int = eqx.field(static=True)
1430
+ num_heads: int = eqx.field(static=True)
1431
+ head_dim: int = eqx.field(static=True)
1432
+ sparse_reg: bool = eqx.field(static=True)
1433
+ sparsity_threshold: float = eqx.field(static=True)
1434
+
1435
+ qkv: eqx.nn.Linear
1436
+ proj: eqx.nn.Linear
1437
+ attn_drop: Dropout
1438
+ proj_drop: Dropout
1439
+ dconv: eqx.nn.Conv
1440
+
1441
+ def __init__(
1442
+ self,
1443
+ dim: int,
1444
+ num_heads: int,
1445
+ sparse_reg: bool = False,
1446
+ *,
1447
+ key: PRNGKeyArray,
1448
+ sparsity_threshold: float = 0.2,
1449
+ qkv_bias: bool = True,
1450
+ proj_bias: bool = True,
1451
+ qk_norm: bool = False,
1452
+ attn_drop: float = 0.0,
1453
+ proj_drop: float = 0.0,
1454
+ res_kernel_size: int = 9,
1455
+ **kwargs,
1456
+ ):
1457
+ self.sparse_reg = sparse_reg
1458
+ self.sparsity_threshold = sparsity_threshold
1459
+ self.num_heads = num_heads
1460
+ self.head_dim = dim // num_heads
1461
+ self.dim = dim
1462
+
1463
+ assert self.head_dim * num_heads == dim, "dim must be divisible by num_heads"
1464
+
1465
+ key_qkv, key_proj, key_dconv = jr.split(key, 3)
1466
+ self.qkv = eqx.nn.Linear(dim, dim * 3, use_bias=qkv_bias, key=key_qkv)
1467
+ self.proj = eqx.nn.Linear(dim, dim, use_bias=proj_bias, key=key_proj)
1468
+
1469
+ self.dconv = eqx.nn.Conv(
1470
+ num_spatial_dims=2,
1471
+ in_channels=num_heads,
1472
+ out_channels=num_heads,
1473
+ kernel_size=(res_kernel_size, 1),
1474
+ stride=1,
1475
+ padding=(res_kernel_size // 2, 0),
1476
+ groups=num_heads,
1477
+ use_bias=False,
1478
+ key=key_dconv,
1479
+ )
1480
+
1481
+ self.attn_drop = Dropout(attn_drop)
1482
+ self.proj_drop = Dropout(proj_drop)
1483
+
1484
+ def __call__(
1485
+ self,
1486
+ x: Float[Array, "seqlen dim"],
1487
+ enable_dropout: bool,
1488
+ key: PRNGKeyArray,
1489
+ ) -> Float[Array, "seqlen dim"]:
1490
+ key1, key2 = jr.split(key, 2)
1491
+
1492
+ qkv = jax.vmap(self.qkv)(x)
1493
+ qkv = rearrange(
1494
+ qkv,
1495
+ "s (n h d) -> n h s d",
1496
+ n=3,
1497
+ h=self.num_heads,
1498
+ d=self.dim // self.num_heads,
1499
+ )
1500
+ q, k, v = qkv
1501
+
1502
+ if self.sparse_reg:
1503
+ attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
1504
+ attn = jax.nn.softmax(attn, axis=-1)
1505
+ attn = self.attn_drop(attn, inference=not enable_dropout, key=key1)
1506
+ sparse = jnp.where(attn > self.sparsity_threshold, attn, 0)
1507
+
1508
+ q = q / jnp.linalg.norm(q, axis=-1, keepdims=True)
1509
+ k = k / jnp.linalg.norm(k, axis=-1, keepdims=True)
1510
+ dconv_v = self.dconv(v)
1511
+
1512
+ attn = jnp.einsum("hqk,hqv->hkv", k, v)
1513
+
1514
+ if self.sparse_reg:
1515
+ x = (sparse @ v) + 0.5 * v + 1.0 / jnp.pi * (q @ attn)
1516
+ else:
1517
+ x = 0.5 * v + 1.0 / jnp.pi * (q @ attn)
1518
+
1519
+ x = x / jnp.linalg.norm(x, axis=-1, keepdims=True)
1520
+ x += dconv_v
1521
+
1522
+ x = rearrange(x, "h s d -> s (h d)")
1523
+ x = jax.vmap(self.proj)(x)
1524
+ x = self.proj_drop(x, inference=not enable_dropout, key=key2)
1525
+
1526
+ return x