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,416 @@
1
+ from typing import 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
8
+ from jaxtyping import Array, Float, PRNGKeyArray
9
+
10
+
11
+ class PosEmbMLPSwinv1D(eqx.Module):
12
+ """1D Positional Embedding using MLP for Swin Transformer.
13
+
14
+ Implements learnable relative position embeddings using an MLP network.
15
+ Supports both 1D sequences and 2D images flattened to 1D.
16
+
17
+ Attributes:
18
+ rank: Dimensionality of position encoding (1 for 1D, 2 for 2D)
19
+ seq_len: Length of input sequence
20
+ cpb_mlp: MLP network for computing position embeddings
21
+ relative_coords_table: Table of relative coordinates (static)
22
+ """
23
+
24
+ rank: int = eqx.field(static=True)
25
+ seq_len: int = eqx.field(static=True)
26
+
27
+ cpb_mlp: eqx.Module
28
+ relative_coords_table: jnp.ndarray = eqx.field(static=True)
29
+
30
+ def __init__(
31
+ self,
32
+ dim: int,
33
+ rank: int,
34
+ seq_len: int,
35
+ *,
36
+ key=PRNGKeyArray,
37
+ **kwargs,
38
+ ):
39
+ key1, key2 = jr.split(key, 2)
40
+ self.rank = rank
41
+ self.seq_len = seq_len
42
+
43
+ self.cpb_mlp = eqx.nn.Sequential(
44
+ [
45
+ eqx.nn.Linear(
46
+ in_features=self.rank,
47
+ out_features=512,
48
+ key=key1,
49
+ ),
50
+ eqx.nn.Lambda(jax.nn.relu),
51
+ eqx.nn.Linear(
52
+ in_features=512,
53
+ out_features=dim,
54
+ use_bias=False,
55
+ key=key2,
56
+ ),
57
+ ]
58
+ )
59
+
60
+ if self.rank == 1:
61
+ relative_coords_h = jnp.arange(0, seq_len)
62
+ relative_coords_h -= seq_len // 2
63
+ relative_coords_h /= seq_len // 2
64
+ self.relative_coords_table = relative_coords_h[:, jnp.newaxis]
65
+ else:
66
+ seq_len = int(seq_len**0.5)
67
+ relative_coords_h = jnp.arange(0, seq_len)
68
+ relative_coords_w = jnp.arange(0, seq_len)
69
+ relative_coords_table = jnp.stack(
70
+ jnp.meshgrid(relative_coords_h, relative_coords_w)
71
+ )
72
+ relative_coords_table -= seq_len // 2
73
+ relative_coords_table /= seq_len // 2
74
+ self.relative_coords_table = relative_coords_table
75
+
76
+ def __call__(
77
+ self,
78
+ x: Float[Array, "..."],
79
+ ) -> Float[Array, "..."]:
80
+ if self.rank == 1:
81
+ table = self.relative_coords_table
82
+ else:
83
+ table = rearrange(self.relative_coords_table, "c h w -> (h w) c")
84
+
85
+ pos_emb = jax.vmap(self.cpb_mlp)(table)
86
+
87
+ return x + pos_emb.astype(x.dtype)
88
+
89
+
90
+ class PosEmbMLPSwinv2D(eqx.Module):
91
+ """2D Positional Embedding using MLP for Swin Transformer V2.
92
+
93
+ Implements learnable relative position embeddings for 2D windows with
94
+ support for cross-window connections and pretrained model adaptation.
95
+
96
+ Attributes:
97
+ ct_correct: Whether to use cross-window token correction
98
+ num_heads: Number of attention heads
99
+ seq_len: Length of input sequence
100
+ window_size: Size of local attention window
101
+ cpb_mlp: MLP for computing position bias
102
+ relative_coords_table: Table of relative coordinates (static)
103
+ relative_position_index: Index mapping for relative positions (static)
104
+ """
105
+
106
+ ct_correct: bool = eqx.field(static=True)
107
+ num_heads: int = eqx.field(static=True)
108
+ seq_len: int = eqx.field(static=True)
109
+ window_size: Tuple[int, int] = eqx.field(static=True)
110
+
111
+ cpb_mlp: eqx.nn.Sequential
112
+ relative_coords_table: jnp.ndarray = eqx.field(static=True)
113
+ relative_position_index: jnp.ndarray = eqx.field(static=True)
114
+
115
+ def __init__(
116
+ self,
117
+ window_size: Tuple[int, int],
118
+ pretrained_window_size: Tuple[int, int],
119
+ num_heads: int,
120
+ seq_len: int,
121
+ *,
122
+ key=PRNGKeyArray,
123
+ inference: bool = False,
124
+ no_log: bool = False,
125
+ ct_correct: bool = False,
126
+ **kwargs,
127
+ ):
128
+ key1, key2 = jr.split(key, 2)
129
+
130
+ self.window_size = window_size
131
+ self.num_heads = num_heads
132
+ self.seq_len = seq_len
133
+ self.ct_correct = ct_correct
134
+
135
+ self.cpb_mlp = eqx.nn.Sequential(
136
+ [
137
+ eqx.nn.Linear(2, 512, use_bias=True, key=key1),
138
+ eqx.nn.Lambda(jax.nn.relu),
139
+ eqx.nn.Linear(512, num_heads, use_bias=False, key=key2),
140
+ ]
141
+ )
142
+
143
+ relative_coords_h = jnp.arange(
144
+ -(self.window_size[0] - 1), self.window_size[0], dtype=jnp.float32
145
+ )
146
+ relative_coords_w = jnp.arange(
147
+ -(self.window_size[1] - 1), self.window_size[1], dtype=jnp.float32
148
+ )
149
+ relative_coords_table = jnp.stack(
150
+ jnp.meshgrid(relative_coords_h, relative_coords_w)
151
+ )
152
+ relative_coords_table = rearrange(
153
+ relative_coords_table,
154
+ "c h w -> 1 h w c",
155
+ )
156
+
157
+ if pretrained_window_size[0] > 0:
158
+ relative_coords_table = relative_coords_table.at[:, :, :, 0].set(
159
+ relative_coords_table[:, :, :, 0] / pretrained_window_size[0] - 1
160
+ )
161
+ relative_coords_table = relative_coords_table.at[:, :, :, 1].set(
162
+ relative_coords_table[:, :, :, 1] / pretrained_window_size[1] - 1
163
+ )
164
+ else:
165
+ relative_coords_table = relative_coords_table.at[:, :, :, 0].set(
166
+ relative_coords_table[:, :, :, 0] / window_size[0] - 1
167
+ )
168
+ relative_coords_table = relative_coords_table.at[:, :, :, 1].set(
169
+ relative_coords_table[:, :, :, 1] / window_size[1] - 1
170
+ )
171
+
172
+ if not no_log:
173
+ relative_coords_table = relative_coords_table * 8
174
+ relative_coords_table = (
175
+ jnp.sign(relative_coords_table)
176
+ * jnp.log2(jnp.abs(relative_coords_table) + 1.0)
177
+ / jnp.log2(8)
178
+ )
179
+
180
+ self.relative_coords_table = relative_coords_table
181
+
182
+ coords_h = jnp.arange(self.window_size[0])
183
+ coords_w = jnp.arange(self.window_size[1])
184
+ coords = jnp.stack(jnp.meshgrid(coords_h, coords_w))
185
+ coords_flatten = coords.reshape(2, -1)
186
+ relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :]
187
+ relative_coords = relative_coords.transpose(1, 2, 0)
188
+ relative_coords = relative_coords + jnp.array(
189
+ [self.window_size[0] - 1, self.window_size[1] - 1]
190
+ )
191
+ relative_coords = relative_coords.at[:, :, 0].set(
192
+ relative_coords[:, :, 0] * (2 * self.window_size[1] - 1)
193
+ )
194
+ self.relative_position_index = jnp.sum(relative_coords, axis=-1)
195
+
196
+ def __call__(
197
+ self, x: Float[Array, "..."], local_window_size: int
198
+ ) -> Float[Array, "..."]:
199
+ relative_position_bias_table = jax.vmap(jax.vmap(jax.vmap(self.cpb_mlp)))(
200
+ self.relative_coords_table
201
+ ).reshape(-1, self.num_heads)
202
+ relative_position_bias = relative_position_bias_table[
203
+ self.relative_position_index.reshape(-1)
204
+ ].reshape(
205
+ self.window_size[0] * self.window_size[1],
206
+ self.window_size[0] * self.window_size[1],
207
+ -1,
208
+ )
209
+ relative_position_bias = jnp.transpose(relative_position_bias, (2, 0, 1))
210
+ relative_position_bias = 16 * jax.nn.sigmoid(relative_position_bias)
211
+
212
+ n_global_feature = x.shape[2] - local_window_size
213
+ if n_global_feature > 0 and self.ct_correct:
214
+ step_for_ct = self.window_size[0] / (n_global_feature**0.5 + 1)
215
+ seq_len = int(n_global_feature**0.5)
216
+ indices = []
217
+
218
+ # TODO: REMOVE THIS FOR LOOPS
219
+ for i in range(seq_len):
220
+ for j in range(seq_len):
221
+ ind = (i + 1) * step_for_ct * self.window_size[0] + (
222
+ j + 1
223
+ ) * step_for_ct
224
+ indices.append(int(ind))
225
+
226
+ top_part = relative_position_bias[:, indices, :]
227
+ lefttop_part = relative_position_bias[:, indices, :][:, :, indices]
228
+ left_part = relative_position_bias[:, :, indices]
229
+
230
+ relative_position_bias = jnp.pad(
231
+ relative_position_bias,
232
+ ((0, 0), (n_global_feature, 0), (n_global_feature, 0)),
233
+ )
234
+
235
+ if n_global_feature > 0 and self.ct_correct:
236
+ relative_position_bias = relative_position_bias * 0.0
237
+ relative_position_bias = relative_position_bias.at[
238
+ :, :n_global_feature, :n_global_feature
239
+ ].set(lefttop_part)
240
+ relative_position_bias = relative_position_bias.at[
241
+ :, :n_global_feature, n_global_feature:
242
+ ].set(top_part)
243
+ relative_position_bias = relative_position_bias.at[
244
+ :, n_global_feature:, :n_global_feature
245
+ ].set(left_part)
246
+
247
+ return x + relative_position_bias.astype(x.dtype)
248
+
249
+
250
+ class RoPE(eqx.Module):
251
+ """Rotary Position Embedding (RoPE).
252
+
253
+ Implements rotary position embeddings that encode positions through
254
+ rotation in complex space. This allows the model to naturally capture
255
+ relative positions through rotational differences.
256
+
257
+ Attributes:
258
+ rotations: Precomputed rotation matrices for position encoding
259
+ """
260
+
261
+ rotations: eqx.Module
262
+
263
+ def __init__(self, shape: tuple, base: int = 10000):
264
+ channel_dims, feature_dim = shape[:-1], shape[-1]
265
+ k_max = feature_dim // (2 * len(channel_dims))
266
+
267
+ if feature_dim % k_max != 0:
268
+ raise ValueError("`feature_dim` is not divisible by `k_max`.")
269
+
270
+ # angles
271
+ theta_ks = jnp.power(base, -jnp.arange(k_max) / k_max)
272
+ angles = jnp.concatenate(
273
+ [
274
+ t[..., None] * theta_ks
275
+ for t in jnp.meshgrid(
276
+ *[jnp.arange(d) for d in channel_dims], indexing="ij"
277
+ )
278
+ ],
279
+ axis=-1,
280
+ )
281
+
282
+ # rotations
283
+ rotations_re = jnp.cos(angles)
284
+ rotations_im = jnp.sin(angles)
285
+ self.rotations = jnp.stack([rotations_re, rotations_im], axis=-1)
286
+
287
+ def __call__(
288
+ self,
289
+ x: Float[Array, "..."],
290
+ ) -> Float[Array, "..."]:
291
+ dtype = x.dtype
292
+ x = x.astype(jnp.float32)
293
+
294
+ # Reshape x to separate real and imaginary parts
295
+ x_reshaped = x.reshape(*x.shape[:-1], -1, 2)
296
+ x_complex = x_reshaped[..., 0] + 1j * x_reshaped[..., 1]
297
+
298
+ # Apply rotation
299
+ rotations_complex = self.rotations[..., 0] + 1j * self.rotations[..., 1]
300
+ pe_x = rotations_complex * x_complex
301
+
302
+ # Convert back to real representation
303
+ pe_x_real = jnp.stack([pe_x.real, pe_x.imag], axis=-1)
304
+
305
+ return pe_x_real.reshape(*x.shape).astype(dtype)
306
+
307
+
308
+ class PosCNN(eqx.Module):
309
+ """Convolutional Position Encoding for 1D sequences.
310
+
311
+ Uses depthwise convolutions to capture local spatial relationships
312
+ and generate position-aware representations. Input is reshaped from
313
+ 1D sequence to 2D for convolution operations.
314
+
315
+ Attributes:
316
+ s: Stride for convolution operation (static)
317
+ proj: Depthwise convolution layer
318
+ """
319
+
320
+ s: int = eqx.field(static=True)
321
+ proj: eqx.nn.Conv
322
+
323
+ def __init__(
324
+ self,
325
+ in_channels: int,
326
+ out_channels: int,
327
+ *,
328
+ key: PRNGKeyArray,
329
+ s: int = 1,
330
+ **kwargs,
331
+ ):
332
+ self.proj = eqx.nn.Conv(
333
+ num_spatial_dims=2,
334
+ in_channels=in_channels,
335
+ out_channels=out_channels,
336
+ groups=out_channels,
337
+ kernel_size=3,
338
+ stride=s,
339
+ padding=1,
340
+ key=key,
341
+ )
342
+
343
+ self.s = s
344
+
345
+ def __call__(
346
+ self,
347
+ x: Float[Array, "seqlen dim"],
348
+ ) -> Float[Array, "seqlen dim"]:
349
+ l, _ = x.shape
350
+ h = w = int(l**0.5)
351
+
352
+ x1 = rearrange(
353
+ self.proj(
354
+ rearrange(
355
+ x,
356
+ "(h w) c -> c h w",
357
+ h=h,
358
+ w=w,
359
+ )
360
+ ),
361
+ "c h w -> (h w) c",
362
+ )
363
+
364
+ if self.s == 1:
365
+ return x + x1
366
+ else:
367
+ return x1
368
+
369
+
370
+ class PosCNN2D(eqx.Module):
371
+ """Convolutional Position Encoding for 2D inputs.
372
+
373
+ Uses depthwise convolutions to capture local spatial relationships
374
+ in 2D feature maps. Similar to PosCNN but operates directly on
375
+ 2D inputs without reshaping.
376
+
377
+ Attributes:
378
+ s: Stride for convolution operation (static)
379
+ proj: Depthwise convolution layer
380
+ """
381
+
382
+ s: int = eqx.field(static=True)
383
+ proj: eqx.nn.Conv
384
+
385
+ def __init__(
386
+ self,
387
+ in_channels: int,
388
+ out_channels: int,
389
+ *,
390
+ key: PRNGKeyArray,
391
+ s: int = 1,
392
+ **kwargs,
393
+ ):
394
+ self.proj = eqx.nn.Conv(
395
+ num_spatial_dims=2,
396
+ in_channels=in_channels,
397
+ out_channels=out_channels,
398
+ groups=out_channels,
399
+ kernel_size=3,
400
+ stride=s,
401
+ padding=1,
402
+ key=key,
403
+ )
404
+
405
+ self.s = s
406
+
407
+ def __call__(
408
+ self,
409
+ x: Float[Array, "channels height width"],
410
+ ) -> Float[Array, "channels height width"]:
411
+ x1 = self.proj(x)
412
+
413
+ if self.s == 1:
414
+ return x + x1
415
+ else:
416
+ return x1
@@ -0,0 +1,160 @@
1
+ import math
2
+ from typing import List
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
+
12
+ class LoRA(eqx.Module):
13
+ """
14
+ Very simple implementation of a LoRA[1] layer.
15
+
16
+ References:
17
+ [1] Hu, et al., LoRA: Low-Rank Adaptation of Large Language Models (2021).
18
+ """
19
+
20
+ scaling: float = eqx.field(static=True)
21
+
22
+ A: Float[Array, "rank in"]
23
+ B: Float[Array, "out rank"]
24
+
25
+ def __init__(
26
+ self,
27
+ in_features: int,
28
+ out_features: int,
29
+ *,
30
+ key: PRNGKeyArray,
31
+ rank: int = 8,
32
+ alpha: int = 16,
33
+ dtype=jnp.float32,
34
+ **kwargs,
35
+ ):
36
+ lim = 1 / math.sqrt(in_features)
37
+ self.A = jr.uniform(
38
+ key,
39
+ (rank, in_features),
40
+ dtype=dtype,
41
+ minval=-lim,
42
+ maxval=lim,
43
+ )
44
+ self.B = jnp.zeros((out_features, rank), dtype=dtype)
45
+
46
+ self.scaling = alpha / rank
47
+
48
+ def __call__(self, x: Array):
49
+ x = self.B @ (self.A @ x)
50
+
51
+ return x * self.scaling
52
+
53
+
54
+ class LayerSharing(eqx.Module):
55
+ """
56
+ Layer Sharing wrapper responsible for repeating a Callable,
57
+ while learning LoRA params. This is similar to what is done in MobileLLM [1]
58
+ and Zamba2 [2]. It allows to use more FLOPs without having to store more params.
59
+
60
+ Depending on the position of this wrapper (block-level or layer-level), given three
61
+ callables A, B and C, you can produce those to kind of repetition patterns:
62
+ - ABCABC,
63
+ - AABBCC.
64
+
65
+ Note:
66
+ Repeating a layer twice requires it to produce outputs of the same shape as its inputs
67
+
68
+ References:
69
+ [1] Liu, et al., MobileLLM: Optimizing Sub-billion Parameter Language Models for On-Device Use Cases (2024).
70
+ [2] https://github.com/Zyphra/Zamba2
71
+ """
72
+
73
+ repeat: int = eqx.field(static=True)
74
+
75
+ loras: List[LoRA]
76
+ dropouts: List[eqx.nn.Dropout]
77
+ f: eqx.Module
78
+
79
+ def __init__(
80
+ self,
81
+ dim: int,
82
+ f: eqx.Module,
83
+ repeat: int,
84
+ *,
85
+ key: PRNGKeyArray,
86
+ rank: int = 8,
87
+ alpha: int = 16,
88
+ drop_rate: float = 0.0,
89
+ **kwargs,
90
+ ):
91
+ assert repeat > 0
92
+
93
+ keys = jr.split(key, repeat)
94
+ self.repeat = repeat
95
+
96
+ self.dropouts = [eqx.nn.Dropout(drop_rate) for i in range(self.repeat)]
97
+ self.loras = [
98
+ LoRA(
99
+ in_features=dim,
100
+ out_features=dim,
101
+ rank=rank,
102
+ alpha=alpha,
103
+ key=keys[i],
104
+ )
105
+ for i in range(self.repeat)
106
+ ]
107
+
108
+ self.f = f
109
+
110
+ def __call__(
111
+ self,
112
+ x: Array,
113
+ *args,
114
+ enable_dropout: bool,
115
+ key: PRNGKeyArray,
116
+ **kwargs,
117
+ ):
118
+ if self.repeat == 1:
119
+ return self.f(
120
+ x,
121
+ *args,
122
+ enable_dropout=enable_dropout,
123
+ key=key,
124
+ **kwargs,
125
+ )
126
+
127
+ keys = jr.split(key, self.repeat)
128
+ reshape = len(x.shape) == 3
129
+
130
+ for i in range(self.repeat):
131
+ if reshape:
132
+ _, h, w = x.shape
133
+ lora_x = rearrange(x, "c h w -> (h w) c")
134
+ else:
135
+ lora_x = x
136
+ lora_output = self.dropouts[i](
137
+ jax.vmap(self.loras[i])(lora_x),
138
+ inference=not enable_dropout,
139
+ key=keys[i],
140
+ )
141
+ if reshape:
142
+ lora_output = rearrange(
143
+ lora_output,
144
+ "(h w) c -> c h w",
145
+ h=h,
146
+ w=w,
147
+ )
148
+
149
+ x = (
150
+ self.f(
151
+ x,
152
+ *args,
153
+ enable_dropout=enable_dropout,
154
+ key=key,
155
+ **kwargs,
156
+ )
157
+ + lora_output
158
+ )
159
+
160
+ return x
@@ -0,0 +1,143 @@
1
+ import equinox as eqx
2
+ import jax
3
+ import jax.random as jr
4
+ from jaxtyping import Array, Float, PRNGKeyArray
5
+
6
+ from equimo.utils import nearest_power_of_2_divisor
7
+
8
+
9
+ def make_divisible(v, divisor=8, min_value=None, round_limit=0.9):
10
+ """
11
+ Helper function to compute the number of channels for the SE Modules.
12
+ Taken from: https://github.com/pprp/timm/blob/e9aac412de82310e6905992e802b1ee4dc52b5d1/timm/layers/helpers.py#L25
13
+ """
14
+ min_value = min_value or divisor
15
+ new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
16
+ if new_v < round_limit * v:
17
+ new_v += divisor
18
+ return new_v
19
+
20
+
21
+ class SEModule(eqx.Module):
22
+ """Squeeze-and-Excite Module as defined in original SE-Nets [1] paper.
23
+
24
+ Implements channel attention mechanism by:
25
+ 1. Squeeze: Global average pooling to capture channel-wise statistics
26
+ 2. Excitation: Two FC layers with reduction to capture channel dependencies
27
+ 3. Scale: Channel-wise multiplication with original features
28
+
29
+ This implementation uses GroupNorm instead of the original BatchNorm for
30
+ better stability in small batch scenarios.
31
+
32
+ Attributes:
33
+ fc1: First conv layer (channel reduction)
34
+ fc2: Second conv layer (channel expansion)
35
+ norm: GroupNorm layer or Identity if use_norm=False
36
+
37
+ Reference:
38
+ [1]: Hu, et al., Squeeze-and-Excitation Networks. 2017.
39
+ https://arxiv.org/abs/1709.01507
40
+ """
41
+
42
+ fc1: eqx.nn.Conv
43
+ fc2: eqx.nn.Conv
44
+ norm: eqx.Module
45
+
46
+ def __init__(
47
+ self,
48
+ dim: int,
49
+ *,
50
+ key: PRNGKeyArray,
51
+ rd_ratio: float = 1.0 / 16,
52
+ rd_divisor: int = 8,
53
+ use_norm: bool = False,
54
+ norm_max_group: int = 32,
55
+ **kwargs,
56
+ ):
57
+ key_fc1, key_fc2 = jr.split(key, 2)
58
+ rd_channels = make_divisible(dim * rd_ratio, rd_divisor, round_limit=0.0)
59
+ self.fc1 = eqx.nn.Conv(
60
+ num_spatial_dims=2,
61
+ in_channels=dim,
62
+ out_channels=rd_channels,
63
+ kernel_size=1,
64
+ stride=1,
65
+ padding=0,
66
+ key=key_fc1,
67
+ )
68
+ num_groups = nearest_power_of_2_divisor(rd_channels, norm_max_group)
69
+ self.norm = (
70
+ eqx.nn.GroupNorm(
71
+ num_groups,
72
+ rd_channels,
73
+ )
74
+ if use_norm
75
+ else eqx.nn.Identity()
76
+ )
77
+ self.fc2 = eqx.nn.Conv(
78
+ num_spatial_dims=2,
79
+ in_channels=rd_channels,
80
+ out_channels=dim,
81
+ kernel_size=1,
82
+ stride=1,
83
+ padding=0,
84
+ key=key_fc2,
85
+ )
86
+
87
+ def __call__(
88
+ self,
89
+ x: Float[Array, "channels height width"],
90
+ ) -> Float[Array, "channels height width"]:
91
+ x_se = x.mean(axis=[1, 2], keepdims=True)
92
+ x_se = jax.nn.relu(self.norm(self.fc1(x_se)))
93
+ x_se = self.fc2(x_se)
94
+
95
+ return x * jax.nn.sigmoid(x_se)
96
+
97
+
98
+ class EffectiveSEModule(eqx.Module):
99
+ """Efficient variant of Squeeze-and-Excitation Module.
100
+
101
+ Simplifies the original SE module by:
102
+ 1. Using a single conv layer instead of two
103
+ 2. Replacing sigmoid with hard_sigmoid activation
104
+ 3. Removing the dimensionality reduction
105
+
106
+ These modifications reduce computational cost while maintaining
107
+ effectiveness for channel attention.
108
+
109
+ Attributes:
110
+ fc: Single convolution layer for channel attention
111
+
112
+ Reference:
113
+ [1]: CenterMask: Real-Time Anchor-Free Instance Segmentation,
114
+ https://arxiv.org/abs/1911.06667
115
+ """
116
+
117
+ fc: eqx.nn.Conv
118
+
119
+ def __init__(
120
+ self,
121
+ dim: int,
122
+ *,
123
+ key: PRNGKeyArray,
124
+ **kwargs,
125
+ ):
126
+ self.fc = eqx.nn.Conv(
127
+ num_spatial_dims=2,
128
+ in_channels=dim,
129
+ out_channels=dim,
130
+ kernel_size=1,
131
+ stride=1,
132
+ padding=0,
133
+ key=key,
134
+ )
135
+
136
+ def __call__(
137
+ self,
138
+ x: Float[Array, "channels height width"],
139
+ ) -> Float[Array, "channels height width"]:
140
+ x_se = x.mean(axis=[1, 2], keepdims=True)
141
+ x_se = self.fc(x_se)
142
+
143
+ return x * jax.nn.hard_sigmoid(x_se)