Equimo 0.4.7__tar.gz → 0.5.0a2__tar.gz
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.4.7/src/Equimo.egg-info → equimo-0.5.0a2}/PKG-INFO +1 -1
- {equimo-0.4.7 → equimo-0.5.0a2}/pyproject.toml +1 -1
- {equimo-0.4.7 → equimo-0.5.0a2/src/Equimo.egg-info}/PKG-INFO +1 -1
- {equimo-0.4.7 → equimo-0.5.0a2}/src/Equimo.egg-info/SOURCES.txt +1 -0
- equimo-0.5.0a2/src/equimo/__init__.py +1 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/attention.py +9 -9
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/convolution.py +315 -17
- equimo-0.5.0a2/src/equimo/layers/downsample.py +209 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/norm.py +31 -17
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/patch.py +18 -21
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/wavelet.py +4 -1
- equimo-0.5.0a2/src/equimo/models/attnet.py +243 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/fastervit.py +8 -4
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/shvit.py +2 -117
- equimo-0.4.7/src/equimo/__init__.py +0 -1
- equimo-0.4.7/src/equimo/layers/downsample.py +0 -58
- {equimo-0.4.7 → equimo-0.5.0a2}/LICENSE.md +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/README.md +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/setup.cfg +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/Equimo.egg-info/dependency_links.txt +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/Equimo.egg-info/requires.txt +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/Equimo.egg-info/top_level.txt +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/conversion/__init__.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/conversion/utils.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/experimental/__init__.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/experimental/text.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/io.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/__init__.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/activation.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/dropout.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/ffn.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/generic.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/mamba.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/posemb.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/sharing.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/layers/squeeze_excite.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/__init__.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/emamodel.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/lowformer.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/mlla.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/partialformer.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/reduceformer.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/vit.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/models/vssd.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/ops/scan.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/src/equimo/utils.py +0 -0
- {equimo-0.4.7 → equimo-0.5.0a2}/tests/test_models.py +0 -0
|
@@ -29,6 +29,7 @@ src/equimo/layers/sharing.py
|
|
|
29
29
|
src/equimo/layers/squeeze_excite.py
|
|
30
30
|
src/equimo/layers/wavelet.py
|
|
31
31
|
src/equimo/models/__init__.py
|
|
32
|
+
src/equimo/models/attnet.py
|
|
32
33
|
src/equimo/models/emamodel.py
|
|
33
34
|
src/equimo/models/fastervit.py
|
|
34
35
|
src/equimo/models/lowformer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0-alpha.2"
|
|
@@ -356,8 +356,8 @@ class AttentionBlock(eqx.Module):
|
|
|
356
356
|
self.norm = norm_layer(dim, eps=eps)
|
|
357
357
|
|
|
358
358
|
if init_values:
|
|
359
|
-
self.ls1 = LayerScale(dim, init_values=init_values)
|
|
360
|
-
self.ls2 = LayerScale(dim, init_values=init_values)
|
|
359
|
+
self.ls1 = LayerScale(dim, axis=1, init_values=init_values)
|
|
360
|
+
self.ls2 = LayerScale(dim, axis=1, init_values=init_values)
|
|
361
361
|
else:
|
|
362
362
|
self.ls1 = self.ls2 = eqx.nn.Identity()
|
|
363
363
|
|
|
@@ -538,8 +538,8 @@ class HATBlock(eqx.Module):
|
|
|
538
538
|
self.norm2 = norm_layer(dim, eps=eps)
|
|
539
539
|
|
|
540
540
|
if init_values:
|
|
541
|
-
self.ls1 = LayerScale(dim, init_values=init_values)
|
|
542
|
-
self.ls2 = LayerScale(dim, init_values=init_values)
|
|
541
|
+
self.ls1 = LayerScale(dim, axis=1, init_values=init_values)
|
|
542
|
+
self.ls2 = LayerScale(dim, axis=1, init_values=init_values)
|
|
543
543
|
else:
|
|
544
544
|
self.ls1 = self.ls2 = eqx.nn.Identity()
|
|
545
545
|
|
|
@@ -611,17 +611,17 @@ class HATBlock(eqx.Module):
|
|
|
611
611
|
dim, rank=2, seq_len=cr_tokens_total, key=key_hatposemb
|
|
612
612
|
)
|
|
613
613
|
self.hat_ls1 = (
|
|
614
|
-
LayerScale(dim, init_values=init_values)
|
|
614
|
+
LayerScale(dim, axis=1, init_values=init_values)
|
|
615
615
|
if init_values
|
|
616
616
|
else eqx.nn.Identity()
|
|
617
617
|
)
|
|
618
618
|
self.hat_ls2 = (
|
|
619
|
-
LayerScale(dim, init_values=init_values)
|
|
619
|
+
LayerScale(dim, axis=1, init_values=init_values)
|
|
620
620
|
if init_values
|
|
621
621
|
else eqx.nn.Identity()
|
|
622
622
|
)
|
|
623
623
|
self.hat_ls3 = (
|
|
624
|
-
LayerScale(dim, init_values=init_values)
|
|
624
|
+
LayerScale(dim, axis=1, init_values=init_values)
|
|
625
625
|
if init_values
|
|
626
626
|
else eqx.nn.Identity()
|
|
627
627
|
)
|
|
@@ -1425,8 +1425,8 @@ class PartialFormerBlock(eqx.Module):
|
|
|
1425
1425
|
self.norm2 = norm_layer(dim, eps=eps)
|
|
1426
1426
|
|
|
1427
1427
|
if init_values:
|
|
1428
|
-
self.ls1 = LayerScale(dim, init_values=init_values)
|
|
1429
|
-
self.ls2 = LayerScale(dim, init_values=init_values)
|
|
1428
|
+
self.ls1 = LayerScale(dim, axis=1, init_values=init_values)
|
|
1429
|
+
self.ls2 = LayerScale(dim, axis=1, init_values=init_values)
|
|
1430
1430
|
else:
|
|
1431
1431
|
self.ls1 = self.ls2 = eqx.nn.Identity()
|
|
1432
1432
|
|
|
@@ -39,7 +39,7 @@ class ConvBlock(eqx.Module):
|
|
|
39
39
|
norm2: eqx.Module
|
|
40
40
|
drop_path1: DropPathAdd
|
|
41
41
|
act: Callable
|
|
42
|
-
ls1: LayerScale |
|
|
42
|
+
ls1: LayerScale | eqx.nn.Identity
|
|
43
43
|
|
|
44
44
|
def __init__(
|
|
45
45
|
self,
|
|
@@ -50,7 +50,7 @@ class ConvBlock(eqx.Module):
|
|
|
50
50
|
kernel_size: int = 3,
|
|
51
51
|
stride: int = 1,
|
|
52
52
|
padding: int = 1,
|
|
53
|
-
act_layer: Callable = jax.nn.gelu,
|
|
53
|
+
act_layer: Callable | None = jax.nn.gelu,
|
|
54
54
|
norm_max_group: int = 32,
|
|
55
55
|
drop_path: float = 0.0,
|
|
56
56
|
init_values: float | None = None,
|
|
@@ -87,7 +87,7 @@ class ConvBlock(eqx.Module):
|
|
|
87
87
|
key=key_conv1,
|
|
88
88
|
)
|
|
89
89
|
self.norm1 = eqx.nn.GroupNorm(num_groups1, hidden_dim)
|
|
90
|
-
self.act = act_layer
|
|
90
|
+
self.act = act_layer if act_layer is not None else eqx.nn.Identity()
|
|
91
91
|
self.conv2 = eqx.nn.Conv(
|
|
92
92
|
num_spatial_dims=2,
|
|
93
93
|
in_channels=hidden_dim,
|
|
@@ -103,18 +103,11 @@ class ConvBlock(eqx.Module):
|
|
|
103
103
|
dpr = drop_path[0] if isinstance(drop_path, list) else float(drop_path)
|
|
104
104
|
self.drop_path1 = DropPathAdd(dpr)
|
|
105
105
|
|
|
106
|
-
self.ls1 =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return rearrange(x, "c h w -> h w c")
|
|
112
|
-
|
|
113
|
-
def depermute(
|
|
114
|
-
self,
|
|
115
|
-
x: Float[Array, "height width channels"],
|
|
116
|
-
) -> Float[Array, "channels height width"]:
|
|
117
|
-
return rearrange(x, "h w c -> c h w")
|
|
106
|
+
self.ls1 = (
|
|
107
|
+
LayerScale(dim, init_values=init_values)
|
|
108
|
+
if init_values
|
|
109
|
+
else eqx.nn.Identity()
|
|
110
|
+
)
|
|
118
111
|
|
|
119
112
|
def __call__(
|
|
120
113
|
self,
|
|
@@ -124,8 +117,7 @@ class ConvBlock(eqx.Module):
|
|
|
124
117
|
) -> Float[Array, "channels height width"]:
|
|
125
118
|
x2 = self.act(self.norm1(self.conv1(x)))
|
|
126
119
|
x2 = self.norm2(self.conv2(x2))
|
|
127
|
-
|
|
128
|
-
x2 = self.depermute(jax.vmap(jax.vmap(self.ls1))(self.permute(x2)))
|
|
120
|
+
x2 = self.ls1(x2)
|
|
129
121
|
|
|
130
122
|
return self.drop_path1(x, x2, inference=inference, key=key)
|
|
131
123
|
|
|
@@ -2017,3 +2009,309 @@ class FasterNetBlock(eqx.Module):
|
|
|
2017
2009
|
out = self.drop_path(x, out, inference=inference, key=key_droppath)
|
|
2018
2010
|
|
|
2019
2011
|
return out
|
|
2012
|
+
|
|
2013
|
+
|
|
2014
|
+
class GLUConv(eqx.Module):
|
|
2015
|
+
conv1: eqx.nn.Conv2d
|
|
2016
|
+
conv2: eqx.nn.Conv2d
|
|
2017
|
+
dwconv: eqx.nn.Conv2d | eqx.nn.Identity
|
|
2018
|
+
norm: eqx.nn.GroupNorm | eqx.nn.Identity
|
|
2019
|
+
act: Callable
|
|
2020
|
+
dr1: eqx.nn.Dropout
|
|
2021
|
+
dr2: eqx.nn.Dropout
|
|
2022
|
+
|
|
2023
|
+
def __init__(
|
|
2024
|
+
self,
|
|
2025
|
+
in_channels: int,
|
|
2026
|
+
hidden_channels: int,
|
|
2027
|
+
*,
|
|
2028
|
+
glu_norm: bool = True,
|
|
2029
|
+
glu_dwconv: bool = False,
|
|
2030
|
+
act_layer: Callable = jax.nn.gelu,
|
|
2031
|
+
dropout: float = 0.0,
|
|
2032
|
+
key: PRNGKeyArray,
|
|
2033
|
+
):
|
|
2034
|
+
key_conv1, key_conv2, key_dwconv = jr.split(key, 3)
|
|
2035
|
+
|
|
2036
|
+
self.conv1 = eqx.nn.Conv2d(
|
|
2037
|
+
in_channels=in_channels,
|
|
2038
|
+
out_channels=hidden_channels * 2,
|
|
2039
|
+
kernel_size=1,
|
|
2040
|
+
stride=1,
|
|
2041
|
+
padding=0,
|
|
2042
|
+
use_bias=True,
|
|
2043
|
+
key=key_conv1,
|
|
2044
|
+
)
|
|
2045
|
+
self.conv2 = eqx.nn.Conv2d(
|
|
2046
|
+
in_channels=hidden_channels,
|
|
2047
|
+
out_channels=in_channels,
|
|
2048
|
+
kernel_size=1,
|
|
2049
|
+
stride=1,
|
|
2050
|
+
padding=0,
|
|
2051
|
+
use_bias=True,
|
|
2052
|
+
key=key_conv2,
|
|
2053
|
+
)
|
|
2054
|
+
self.dwconv = (
|
|
2055
|
+
eqx.nn.Conv2d(
|
|
2056
|
+
in_channels=hidden_channels,
|
|
2057
|
+
out_channels=hidden_channels,
|
|
2058
|
+
kernel_size=3,
|
|
2059
|
+
stride=1,
|
|
2060
|
+
padding=1,
|
|
2061
|
+
groups=hidden_channels,
|
|
2062
|
+
use_bias=True,
|
|
2063
|
+
key=key_dwconv,
|
|
2064
|
+
)
|
|
2065
|
+
if glu_dwconv
|
|
2066
|
+
else eqx.nn.Identity()
|
|
2067
|
+
)
|
|
2068
|
+
|
|
2069
|
+
num_groups = nearest_power_of_2_divisor(in_channels, 32)
|
|
2070
|
+
self.norm = (
|
|
2071
|
+
eqx.nn.GroupNorm(num_groups, hidden_channels)
|
|
2072
|
+
if glu_norm
|
|
2073
|
+
else eqx.nn.Identity()
|
|
2074
|
+
)
|
|
2075
|
+
|
|
2076
|
+
self.act = act_layer
|
|
2077
|
+
self.dr1 = eqx.nn.Dropout(dropout)
|
|
2078
|
+
self.dr2 = eqx.nn.Dropout(dropout)
|
|
2079
|
+
|
|
2080
|
+
def __call__(
|
|
2081
|
+
self,
|
|
2082
|
+
x: Float[Array, "channels height width"],
|
|
2083
|
+
key: PRNGKeyArray,
|
|
2084
|
+
inference: Optional[bool] = None,
|
|
2085
|
+
) -> Float[Array, "channels height width"]:
|
|
2086
|
+
key_dr1, key_dr2 = jr.split(key, 2)
|
|
2087
|
+
|
|
2088
|
+
x, v = jnp.split(self.conv1(x), 2)
|
|
2089
|
+
|
|
2090
|
+
x = self.norm(self.act(self.dwconv(x)) * v)
|
|
2091
|
+
x = self.dr1(x, inference=inference, key=key_dr1)
|
|
2092
|
+
x = self.conv2(x)
|
|
2093
|
+
x = self.dr2(x, inference=inference, key=key_dr2)
|
|
2094
|
+
|
|
2095
|
+
return x
|
|
2096
|
+
|
|
2097
|
+
|
|
2098
|
+
class ATConv(eqx.Module):
|
|
2099
|
+
"""
|
|
2100
|
+
Attentive Convolution (ATConv) layer that generates per-sample, per-channel 3×3 dynamic kernels
|
|
2101
|
+
and applies them as a depthwise grouped 2D convolution with linear-time cost, capturing adaptive
|
|
2102
|
+
routing and lateral inhibition principles identified as key advantages of self-attention over static
|
|
2103
|
+
convolutions[1].
|
|
2104
|
+
|
|
2105
|
+
The operator first projects features, synthesizes a per-channel HWIO kernel from pooled spatial
|
|
2106
|
+
summaries, subtracts a learnable fraction of its channel-wise mean to induce inhibition, then
|
|
2107
|
+
performs a depthwise convolution followed by a 1×1 projection, matching the reference design
|
|
2108
|
+
that unifies attention-like expressivity with convolutional efficiency[1].
|
|
2109
|
+
|
|
2110
|
+
Referencea:
|
|
2111
|
+
[1]. Yu, et al., Attentive Convolution: Unifying the Expressivity of Self-Attention with Convolutional
|
|
2112
|
+
Efficiency. 2025. arXiv:2510.20092
|
|
2113
|
+
"""
|
|
2114
|
+
|
|
2115
|
+
kernel_size: int = eqx.field(static=True)
|
|
2116
|
+
padding: int = eqx.field(static=True)
|
|
2117
|
+
|
|
2118
|
+
x_proj: eqx.nn.Conv2d
|
|
2119
|
+
proj: eqx.nn.Conv2d
|
|
2120
|
+
kernel_proj: eqx.nn.Conv2d
|
|
2121
|
+
kernel_act: Callable
|
|
2122
|
+
kernel_gen: eqx.nn.Linear
|
|
2123
|
+
pool: eqx.nn.AdaptiveAvgPool1d
|
|
2124
|
+
difference_control: jax.Array
|
|
2125
|
+
|
|
2126
|
+
def __init__(
|
|
2127
|
+
self,
|
|
2128
|
+
in_channels: int,
|
|
2129
|
+
*,
|
|
2130
|
+
kernel_size: int = 3,
|
|
2131
|
+
act_layer: Callable = jax.nn.gelu,
|
|
2132
|
+
use_bias: bool = True,
|
|
2133
|
+
key: PRNGKeyArray,
|
|
2134
|
+
):
|
|
2135
|
+
key_xproj, key_proj, key_kp, key_kg = jr.split(key, 4)
|
|
2136
|
+
|
|
2137
|
+
self.kernel_size = kernel_size
|
|
2138
|
+
k2 = kernel_size**2
|
|
2139
|
+
self.padding = kernel_size // 2
|
|
2140
|
+
|
|
2141
|
+
self.x_proj = eqx.nn.Conv2d(
|
|
2142
|
+
in_channels,
|
|
2143
|
+
in_channels,
|
|
2144
|
+
kernel_size=1,
|
|
2145
|
+
stride=1,
|
|
2146
|
+
padding=0,
|
|
2147
|
+
use_bias=use_bias,
|
|
2148
|
+
key=key_xproj,
|
|
2149
|
+
)
|
|
2150
|
+
self.proj = eqx.nn.Conv2d(
|
|
2151
|
+
in_channels,
|
|
2152
|
+
in_channels,
|
|
2153
|
+
kernel_size=1,
|
|
2154
|
+
stride=1,
|
|
2155
|
+
padding=0,
|
|
2156
|
+
use_bias=use_bias,
|
|
2157
|
+
key=key_proj,
|
|
2158
|
+
)
|
|
2159
|
+
self.kernel_proj = eqx.nn.Conv2d(
|
|
2160
|
+
in_channels=in_channels,
|
|
2161
|
+
out_channels=in_channels,
|
|
2162
|
+
kernel_size=1,
|
|
2163
|
+
stride=1,
|
|
2164
|
+
padding=0,
|
|
2165
|
+
use_bias=use_bias,
|
|
2166
|
+
key=key_kp,
|
|
2167
|
+
)
|
|
2168
|
+
self.kernel_act = act_layer
|
|
2169
|
+
self.kernel_gen = eqx.nn.Linear(k2, k2, use_bias=use_bias, key=key_kg)
|
|
2170
|
+
self.pool = eqx.nn.AdaptiveAvgPool1d(target_shape=k2)
|
|
2171
|
+
self.difference_control = jnp.zeros((1, 1, 1, in_channels))
|
|
2172
|
+
|
|
2173
|
+
def _generate_kernels(
|
|
2174
|
+
self, x: Float[Array, "channels height width"]
|
|
2175
|
+
) -> Float[Array, "k k 1 channels"]:
|
|
2176
|
+
kernels = self.kernel_proj(x)
|
|
2177
|
+
kernels = self.pool(rearrange(kernels, "c h w -> c (h w)"))
|
|
2178
|
+
kernels = self.kernel_act(kernels)
|
|
2179
|
+
kernels = jax.vmap(self.kernel_gen)(kernels)
|
|
2180
|
+
kernels = rearrange(
|
|
2181
|
+
kernels, "c (k1 k2) -> k1 k2 1 c", k1=self.kernel_size, k2=self.kernel_size
|
|
2182
|
+
)
|
|
2183
|
+
|
|
2184
|
+
return kernels
|
|
2185
|
+
|
|
2186
|
+
def _apply_kernel_difference(
|
|
2187
|
+
self, kernels: Float[Array, "channels k k"]
|
|
2188
|
+
) -> Float[Array, "channels k k"]:
|
|
2189
|
+
mean_kernels = kernels.mean(axis=(0, 1, 2), keepdims=True)
|
|
2190
|
+
factor = jax.nn.sigmoid(self.difference_control)
|
|
2191
|
+
|
|
2192
|
+
return kernels - factor * mean_kernels
|
|
2193
|
+
|
|
2194
|
+
def __call__(
|
|
2195
|
+
self,
|
|
2196
|
+
x: Float[Array, "channels height width"],
|
|
2197
|
+
key: PRNGKeyArray,
|
|
2198
|
+
inference: Optional[bool] = None,
|
|
2199
|
+
) -> Float[Array, "channels height width"]:
|
|
2200
|
+
kernels = self._generate_kernels(x)
|
|
2201
|
+
kernels = self._apply_kernel_difference(kernels)
|
|
2202
|
+
|
|
2203
|
+
x = self.x_proj(x)
|
|
2204
|
+
C, H, W = x.shape
|
|
2205
|
+
x = jax.lax.conv_general_dilated(
|
|
2206
|
+
x[None, ...], # (N, C, H, W)
|
|
2207
|
+
kernels, # (H, W, I=1, O=C)
|
|
2208
|
+
window_strides=(1, 1),
|
|
2209
|
+
padding=((self.padding, self.padding), (self.padding, self.padding)),
|
|
2210
|
+
lhs_dilation=None,
|
|
2211
|
+
rhs_dilation=None,
|
|
2212
|
+
dimension_numbers=("NCHW", "HWIO", "NCHW"),
|
|
2213
|
+
feature_group_count=C, # depthwise
|
|
2214
|
+
)[0]
|
|
2215
|
+
|
|
2216
|
+
x = self.proj(x)
|
|
2217
|
+
|
|
2218
|
+
return x
|
|
2219
|
+
|
|
2220
|
+
|
|
2221
|
+
class ATConvBlock(eqx.Module):
|
|
2222
|
+
token_mixer: ATConv
|
|
2223
|
+
channel_mixer: GLUConv
|
|
2224
|
+
ls1: LayerScale | eqx.nn.Identity
|
|
2225
|
+
ls2: LayerScale | eqx.nn.Identity
|
|
2226
|
+
norm1: eqx.nn.LayerNorm
|
|
2227
|
+
norm2: eqx.nn.LayerNorm
|
|
2228
|
+
drop_path1: DropPathAdd
|
|
2229
|
+
drop_path2: DropPathAdd
|
|
2230
|
+
|
|
2231
|
+
def __init__(
|
|
2232
|
+
self,
|
|
2233
|
+
in_channels: int,
|
|
2234
|
+
*,
|
|
2235
|
+
kernel_size: int = 3,
|
|
2236
|
+
exp_rate: float = 4.0,
|
|
2237
|
+
act_layer: Callable = jax.nn.gelu,
|
|
2238
|
+
glu_norm: bool = True,
|
|
2239
|
+
glu_dwconv: bool = False,
|
|
2240
|
+
use_bias: bool = True,
|
|
2241
|
+
dropout: float = 0.0,
|
|
2242
|
+
drop_path: float | list[float] = 0.0,
|
|
2243
|
+
use_layer_scale: bool = True,
|
|
2244
|
+
key: PRNGKeyArray,
|
|
2245
|
+
):
|
|
2246
|
+
key_tm, key_cm = jr.split(key, 2)
|
|
2247
|
+
|
|
2248
|
+
hidden_features = int(in_channels * exp_rate)
|
|
2249
|
+
# NOTE: slightly different from the original paper
|
|
2250
|
+
glu_hidden_features = 32 * round(int(2 * hidden_features / 3) / 32)
|
|
2251
|
+
|
|
2252
|
+
self.token_mixer = ATConv(
|
|
2253
|
+
in_channels,
|
|
2254
|
+
kernel_size=kernel_size,
|
|
2255
|
+
act_layer=act_layer,
|
|
2256
|
+
use_bias=use_bias,
|
|
2257
|
+
key=key_tm,
|
|
2258
|
+
)
|
|
2259
|
+
self.channel_mixer = GLUConv(
|
|
2260
|
+
in_channels,
|
|
2261
|
+
hidden_channels=glu_hidden_features,
|
|
2262
|
+
glu_norm=glu_norm,
|
|
2263
|
+
glu_dwconv=glu_dwconv,
|
|
2264
|
+
act_layer=act_layer,
|
|
2265
|
+
dropout=dropout,
|
|
2266
|
+
key=key_cm,
|
|
2267
|
+
)
|
|
2268
|
+
|
|
2269
|
+
self.ls1 = LayerScale(in_channels) if use_layer_scale else eqx.nn.Identity()
|
|
2270
|
+
self.ls2 = LayerScale(in_channels) if use_layer_scale else eqx.nn.Identity()
|
|
2271
|
+
|
|
2272
|
+
self.norm1 = eqx.nn.LayerNorm(in_channels)
|
|
2273
|
+
self.norm2 = eqx.nn.LayerNorm(in_channels)
|
|
2274
|
+
|
|
2275
|
+
if isinstance(drop_path, list):
|
|
2276
|
+
if len(drop_path) != 2:
|
|
2277
|
+
raise AssertionError(
|
|
2278
|
+
f"`drop_path` needs to have 2 elements, got {len(drop_path)} ({drop_path})."
|
|
2279
|
+
)
|
|
2280
|
+
dr1, dr2 = drop_path
|
|
2281
|
+
dr1 = float(dr1)
|
|
2282
|
+
dr2 = float(dr2)
|
|
2283
|
+
else:
|
|
2284
|
+
dr1 = dr2 = float(drop_path)
|
|
2285
|
+
|
|
2286
|
+
self.drop_path1 = DropPathAdd(dr1)
|
|
2287
|
+
self.drop_path2 = DropPathAdd(dr2)
|
|
2288
|
+
|
|
2289
|
+
def __call__(
|
|
2290
|
+
self,
|
|
2291
|
+
x: Float[Array, "channels height width"],
|
|
2292
|
+
key: PRNGKeyArray,
|
|
2293
|
+
inference: Optional[bool] = None,
|
|
2294
|
+
) -> Float[Array, "channels height width"]:
|
|
2295
|
+
key_tm, key_cm, key_dr1, key_dr2 = jr.split(key, 4)
|
|
2296
|
+
|
|
2297
|
+
norm1 = jax.vmap(
|
|
2298
|
+
jax.vmap(self.norm1, in_axes=1, out_axes=1), in_axes=1, out_axes=1
|
|
2299
|
+
)
|
|
2300
|
+
norm2 = jax.vmap(
|
|
2301
|
+
jax.vmap(self.norm2, in_axes=1, out_axes=1), in_axes=1, out_axes=1
|
|
2302
|
+
)
|
|
2303
|
+
|
|
2304
|
+
x = self.drop_path1(
|
|
2305
|
+
x,
|
|
2306
|
+
self.ls1(self.token_mixer(norm1(x), inference=inference, key=key_tm)),
|
|
2307
|
+
inference=inference,
|
|
2308
|
+
key=key_dr1,
|
|
2309
|
+
)
|
|
2310
|
+
x = self.drop_path2(
|
|
2311
|
+
x,
|
|
2312
|
+
self.ls2(self.channel_mixer(norm2(x), inference=inference, key=key_cm)),
|
|
2313
|
+
inference=inference,
|
|
2314
|
+
key=key_dr2,
|
|
2315
|
+
)
|
|
2316
|
+
|
|
2317
|
+
return x
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from typing import Callable, Literal, Optional
|
|
2
|
+
|
|
3
|
+
import equinox as eqx
|
|
4
|
+
import jax
|
|
5
|
+
import jax.random as jr
|
|
6
|
+
from jaxtyping import Array, Float, PRNGKeyArray
|
|
7
|
+
|
|
8
|
+
from equimo.layers.convolution import ConvBlock, SingleConvBlock
|
|
9
|
+
from equimo.layers.generic import Residual
|
|
10
|
+
from equimo.layers.patch import SEPatchMerging
|
|
11
|
+
from equimo.utils import nearest_power_of_2_divisor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConvNormDownsampler(eqx.Module):
|
|
15
|
+
"""A module that performs spatial downsampling using strided convolution.
|
|
16
|
+
|
|
17
|
+
This module reduces spatial dimensions (height and width) by a factor of 2
|
|
18
|
+
while optionally increasing the channel dimension. Uses a 3x3 strided
|
|
19
|
+
convolution for downsampling.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
reduction: Convolutional layer that performs the downsampling
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
downsampler: eqx.nn.Sequential
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
in_channels: int,
|
|
30
|
+
*,
|
|
31
|
+
out_channels: int | None = None,
|
|
32
|
+
act_layer: Callable = jax.nn.gelu,
|
|
33
|
+
use_bias: bool = False,
|
|
34
|
+
use_norm: bool = True,
|
|
35
|
+
mode: Literal["double", "simple"] = "simple",
|
|
36
|
+
key: PRNGKeyArray,
|
|
37
|
+
):
|
|
38
|
+
out_channels = out_channels if out_channels else 2 * in_channels
|
|
39
|
+
|
|
40
|
+
match mode:
|
|
41
|
+
case "simple":
|
|
42
|
+
self.downsampler = eqx.nn.Sequential(
|
|
43
|
+
[
|
|
44
|
+
eqx.nn.Conv2d(
|
|
45
|
+
in_channels=in_channels,
|
|
46
|
+
out_channels=out_channels,
|
|
47
|
+
kernel_size=3,
|
|
48
|
+
stride=2,
|
|
49
|
+
padding=1,
|
|
50
|
+
use_bias=False,
|
|
51
|
+
key=key,
|
|
52
|
+
),
|
|
53
|
+
eqx.nn.GroupNorm(
|
|
54
|
+
nearest_power_of_2_divisor(out_channels, 32), out_channels
|
|
55
|
+
)
|
|
56
|
+
if use_norm
|
|
57
|
+
else eqx.nn.Identity(),
|
|
58
|
+
]
|
|
59
|
+
)
|
|
60
|
+
case "double":
|
|
61
|
+
self.downsampler = eqx.nn.Sequential(
|
|
62
|
+
[
|
|
63
|
+
eqx.nn.Conv2d(
|
|
64
|
+
in_channels=in_channels,
|
|
65
|
+
out_channels=(_d := out_channels // 2),
|
|
66
|
+
kernel_size=3,
|
|
67
|
+
stride=2,
|
|
68
|
+
padding=1,
|
|
69
|
+
use_bias=use_bias,
|
|
70
|
+
key=jr.fold_in(key, 0),
|
|
71
|
+
),
|
|
72
|
+
eqx.nn.GroupNorm(nearest_power_of_2_divisor(_d, 32), _d)
|
|
73
|
+
if use_norm
|
|
74
|
+
else eqx.nn.Identity(),
|
|
75
|
+
eqx.nn.Lambda(act_layer),
|
|
76
|
+
eqx.nn.Conv2d(
|
|
77
|
+
in_channels=_d,
|
|
78
|
+
out_channels=out_channels,
|
|
79
|
+
kernel_size=3,
|
|
80
|
+
stride=2,
|
|
81
|
+
padding=1,
|
|
82
|
+
use_bias=use_bias,
|
|
83
|
+
key=jr.fold_in(key, 1),
|
|
84
|
+
),
|
|
85
|
+
eqx.nn.GroupNorm(
|
|
86
|
+
nearest_power_of_2_divisor(out_channels, 32), out_channels
|
|
87
|
+
)
|
|
88
|
+
if use_norm
|
|
89
|
+
else eqx.nn.Identity(),
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def __call__(self, x, *args, **kwargs):
|
|
94
|
+
return self.downsampler(x)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class PWSEDownsampler(eqx.Module):
|
|
98
|
+
"""Downsampling module for spatial feature reduction.
|
|
99
|
+
|
|
100
|
+
Combines convolution blocks and patch merging to reduce spatial dimensions
|
|
101
|
+
while increasing feature channels. Uses residual connections and alternates
|
|
102
|
+
between depthwise and pointwise convolutions.
|
|
103
|
+
|
|
104
|
+
Attributes:
|
|
105
|
+
conv1: First depthwise convolution with residual
|
|
106
|
+
conv2: First pointwise convolution block
|
|
107
|
+
conv3: Second depthwise convolution with residual
|
|
108
|
+
conv4: Second pointwise convolution block
|
|
109
|
+
patch_merging: Squeeze-and-excitation patch merging layer
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
conv1: eqx.Module
|
|
113
|
+
conv2: eqx.Module
|
|
114
|
+
conv3: eqx.Module
|
|
115
|
+
conv4: eqx.Module
|
|
116
|
+
patch_merging: eqx.Module
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
in_channels: int,
|
|
121
|
+
out_channels: int,
|
|
122
|
+
*,
|
|
123
|
+
key: PRNGKeyArray,
|
|
124
|
+
drop_path: float = 0.0,
|
|
125
|
+
**kwargs,
|
|
126
|
+
):
|
|
127
|
+
key_conv1, key_conv2, key_conv3, key_conv4, key_pm = jr.split(key, 5)
|
|
128
|
+
self.conv1 = Residual(
|
|
129
|
+
SingleConvBlock(
|
|
130
|
+
in_channels,
|
|
131
|
+
in_channels,
|
|
132
|
+
act_layer=None,
|
|
133
|
+
kernel_size=3,
|
|
134
|
+
stride=1,
|
|
135
|
+
padding=1,
|
|
136
|
+
groups=in_channels,
|
|
137
|
+
key=key_conv1,
|
|
138
|
+
),
|
|
139
|
+
drop_path=drop_path,
|
|
140
|
+
)
|
|
141
|
+
self.conv2 = ConvBlock(
|
|
142
|
+
in_channels,
|
|
143
|
+
hidden_in_channels=in_channels * 2,
|
|
144
|
+
act_layer=None,
|
|
145
|
+
drop_path=drop_path,
|
|
146
|
+
kernel_size=1,
|
|
147
|
+
stride=1,
|
|
148
|
+
padding=0,
|
|
149
|
+
key=key_conv2,
|
|
150
|
+
)
|
|
151
|
+
self.patch_merging = SEPatchMerging(
|
|
152
|
+
in_channels=in_channels,
|
|
153
|
+
out_channels=out_channels,
|
|
154
|
+
key=key_pm,
|
|
155
|
+
)
|
|
156
|
+
self.conv3 = Residual(
|
|
157
|
+
SingleConvBlock(
|
|
158
|
+
out_channels,
|
|
159
|
+
out_channels,
|
|
160
|
+
act_layer=None,
|
|
161
|
+
kernel_size=3,
|
|
162
|
+
stride=1,
|
|
163
|
+
padding=1,
|
|
164
|
+
groups=out_channels,
|
|
165
|
+
key=key_conv3,
|
|
166
|
+
),
|
|
167
|
+
drop_path=drop_path,
|
|
168
|
+
)
|
|
169
|
+
self.conv4 = ConvBlock(
|
|
170
|
+
out_channels,
|
|
171
|
+
hidden_in_channels=out_channels * 2,
|
|
172
|
+
act_layer=None,
|
|
173
|
+
drop_path=drop_path,
|
|
174
|
+
kernel_size=1,
|
|
175
|
+
stride=1,
|
|
176
|
+
padding=0,
|
|
177
|
+
key=key_conv4,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def __call__(
|
|
181
|
+
self,
|
|
182
|
+
x: Float[Array, "seqlen dim"],
|
|
183
|
+
key: PRNGKeyArray,
|
|
184
|
+
inference: Optional[bool] = None,
|
|
185
|
+
) -> Float[Array, "seqlen dim"]:
|
|
186
|
+
"""Apply downsampling to input features.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
x: Input feature tensor
|
|
190
|
+
inference: Whether to enable dropout during inference
|
|
191
|
+
key: PRNG key for random operations
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Downsampled feature tensor with increased channels
|
|
195
|
+
"""
|
|
196
|
+
key_conv1, key_conv2, key_conv3, key_conv4 = jr.split(key, 4)
|
|
197
|
+
x = self.conv2(
|
|
198
|
+
self.conv1(x, inference=inference, key=key_conv1),
|
|
199
|
+
inference=inference,
|
|
200
|
+
key=key_conv2,
|
|
201
|
+
)
|
|
202
|
+
x = self.patch_merging(x)
|
|
203
|
+
x = self.conv4(
|
|
204
|
+
self.conv3(x, inference=inference, key=key_conv3),
|
|
205
|
+
inference=inference,
|
|
206
|
+
key=key_conv4,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return x
|