Equimo 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Equimo-0.1.0.dist-info/LICENSE.md +21 -0
- Equimo-0.1.0.dist-info/METADATA +104 -0
- Equimo-0.1.0.dist-info/RECORD +29 -0
- Equimo-0.1.0.dist-info/WHEEL +5 -0
- Equimo-0.1.0.dist-info/top_level.txt +1 -0
- equimo/__init__.py +0 -0
- equimo/layers/__init__.py +0 -0
- equimo/layers/attention.py +1526 -0
- equimo/layers/convolution.py +329 -0
- equimo/layers/downsample.py +58 -0
- equimo/layers/dropout.py +217 -0
- equimo/layers/ffn.py +184 -0
- equimo/layers/generic.py +68 -0
- equimo/layers/mamba.py +169 -0
- equimo/layers/norm.py +87 -0
- equimo/layers/patch.py +399 -0
- equimo/layers/posemb.py +416 -0
- equimo/layers/sharing.py +160 -0
- equimo/layers/squeeze_excite.py +143 -0
- equimo/models/__init__.py +7 -0
- equimo/models/emamodel.py +99 -0
- equimo/models/fastervit.py +488 -0
- equimo/models/mlla.py +166 -0
- equimo/models/partialformer.py +407 -0
- equimo/models/shvit.py +376 -0
- equimo/models/vit.py +472 -0
- equimo/models/vssd.py +189 -0
- equimo/ops/scan.py +246 -0
- equimo/utils.py +127 -0
equimo/layers/posemb.py
ADDED
|
@@ -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
|
equimo/layers/sharing.py
ADDED
|
@@ -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)
|