Equimo 0.4.0a15__tar.gz → 0.4.2__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.0a15/src/Equimo.egg-info → equimo-0.4.2}/PKG-INFO +3 -2
- {equimo-0.4.0a15 → equimo-0.4.2}/README.md +1 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/pyproject.toml +2 -2
- {equimo-0.4.0a15 → equimo-0.4.2/src/Equimo.egg-info}/PKG-INFO +3 -2
- equimo-0.4.2/src/equimo/__init__.py +1 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/conversion/utils.py +30 -16
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/io.py +27 -1
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/attention.py +83 -1
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/convolution.py +3 -3
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/ffn.py +95 -27
- equimo-0.4.2/src/equimo/layers/posemb.py +753 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/reduceformer.py +7 -6
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/vit.py +94 -113
- {equimo-0.4.0a15 → equimo-0.4.2}/tests/test_models.py +11 -0
- equimo-0.4.0a15/src/equimo/__init__.py +0 -1
- equimo-0.4.0a15/src/equimo/layers/posemb.py +0 -416
- {equimo-0.4.0a15 → equimo-0.4.2}/LICENSE.md +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/setup.cfg +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/SOURCES.txt +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/dependency_links.txt +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/requires.txt +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/top_level.txt +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/conversion/__init__.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/experimental/__init__.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/experimental/text.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/__init__.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/activation.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/downsample.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/dropout.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/generic.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/mamba.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/norm.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/patch.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/sharing.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/squeeze_excite.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/__init__.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/emamodel.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/fastervit.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/lowformer.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/mlla.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/partialformer.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/shvit.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/vssd.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/ops/scan.py +0 -0
- {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Equimo
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Implementation of popular vision models in Jax
|
|
5
5
|
Classifier: Development Status :: 4 - Beta
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE.md
|
|
17
17
|
Requires-Dist: einops>=0.8.0
|
|
@@ -78,6 +78,7 @@ Beyond normal ViT (e.g., dinov2 or siglip), equimo proposes other SotA architect
|
|
|
78
78
|
| VSSD | [VSSD: Vision Mamba with Non-Causal State Space Duality](https://arxiv.org/abs/2407.18559) | 2024 | ✅ |
|
|
79
79
|
| ReduceFormer | [ReduceFormer: Attention with Tensor Reduction by Summation](https://arxiv.org/abs/2406.07488) | 2024 | ✅ |
|
|
80
80
|
| LowFormer | [LowFormer: Hardware Efficient Design for Convolutional Transformer Backbones](https://arxiv.org/abs/2409.03460) | 2024 | ✅ |
|
|
81
|
+
| DINOv3 | [DINOv3](https://arxiv.org/abs/2508.10104) | 2025 | ✅ |
|
|
81
82
|
|
|
82
83
|
\*: Only contains the [Linear Angular Attention](https://github.com/clementpoiret/Equimo/blob/f8fcc79e45ca65e9deb1d970c4286c0b8562f9c2/equimo/layers/attention.py#L1407) module. It is straight forward to build a ViT around it, but may require an additional `__call__` kwarg to control the `sparse_reg` bool.
|
|
83
84
|
|
|
@@ -42,6 +42,7 @@ Beyond normal ViT (e.g., dinov2 or siglip), equimo proposes other SotA architect
|
|
|
42
42
|
| VSSD | [VSSD: Vision Mamba with Non-Causal State Space Duality](https://arxiv.org/abs/2407.18559) | 2024 | ✅ |
|
|
43
43
|
| ReduceFormer | [ReduceFormer: Attention with Tensor Reduction by Summation](https://arxiv.org/abs/2406.07488) | 2024 | ✅ |
|
|
44
44
|
| LowFormer | [LowFormer: Hardware Efficient Design for Convolutional Transformer Backbones](https://arxiv.org/abs/2409.03460) | 2024 | ✅ |
|
|
45
|
+
| DINOv3 | [DINOv3](https://arxiv.org/abs/2508.10104) | 2025 | ✅ |
|
|
45
46
|
|
|
46
47
|
\*: Only contains the [Linear Angular Attention](https://github.com/clementpoiret/Equimo/blob/f8fcc79e45ca65e9deb1d970c4286c0b8562f9c2/equimo/layers/attention.py#L1407) module. It is straight forward to build a ViT around it, but may require an additional `__call__` kwarg to control the `sparse_reg` bool.
|
|
47
48
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Equimo"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.2"
|
|
4
4
|
description = "Implementation of popular vision models in Jax"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"einops>=0.8.0",
|
|
9
9
|
"equinox>=0.11.5",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Equimo
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Implementation of popular vision models in Jax
|
|
5
5
|
Classifier: Development Status :: 4 - Beta
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE.md
|
|
17
17
|
Requires-Dist: einops>=0.8.0
|
|
@@ -78,6 +78,7 @@ Beyond normal ViT (e.g., dinov2 or siglip), equimo proposes other SotA architect
|
|
|
78
78
|
| VSSD | [VSSD: Vision Mamba with Non-Causal State Space Duality](https://arxiv.org/abs/2407.18559) | 2024 | ✅ |
|
|
79
79
|
| ReduceFormer | [ReduceFormer: Attention with Tensor Reduction by Summation](https://arxiv.org/abs/2406.07488) | 2024 | ✅ |
|
|
80
80
|
| LowFormer | [LowFormer: Hardware Efficient Design for Convolutional Transformer Backbones](https://arxiv.org/abs/2409.03460) | 2024 | ✅ |
|
|
81
|
+
| DINOv3 | [DINOv3](https://arxiv.org/abs/2508.10104) | 2025 | ✅ |
|
|
81
82
|
|
|
82
83
|
\*: Only contains the [Linear Angular Attention](https://github.com/clementpoiret/Equimo/blob/f8fcc79e45ca65e9deb1d970c4286c0b8562f9c2/equimo/layers/attention.py#L1407) module. It is straight forward to build a ViT around it, but may require an additional `__call__` kwarg to control the `sparse_reg` bool.
|
|
83
84
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.2"
|
|
@@ -36,10 +36,11 @@ def convert_params_from_torch(
|
|
|
36
36
|
replace_cfg: Dict[str, str],
|
|
37
37
|
expand_cfg: Dict[str, list],
|
|
38
38
|
squeeze_cfg: Dict[str, int | None],
|
|
39
|
-
|
|
39
|
+
torch_whitelist: list[str],
|
|
40
|
+
jax_whitelist: list[str],
|
|
40
41
|
strict: bool = True,
|
|
41
42
|
source: Literal["torchhub", "timm", "custom"] = "torchhub",
|
|
42
|
-
torch_hub_cfg: Optional[
|
|
43
|
+
torch_hub_cfg: Optional[dict] = None,
|
|
43
44
|
torch_model=None,
|
|
44
45
|
timm_cfg: Optional[list] = None,
|
|
45
46
|
return_torch: bool = False,
|
|
@@ -49,14 +50,15 @@ def convert_params_from_torch(
|
|
|
49
50
|
|
|
50
51
|
Args:
|
|
51
52
|
jax_model (eqx.Module): A preexisting Jax model corresponding to the checkpoint to download.
|
|
52
|
-
torch_hub_cfg (
|
|
53
|
+
torch_hub_cfg (dict): Arguments passed to `torch.hub.load()`.
|
|
53
54
|
replace_cfg (Dict[str, str]): Rename parameters from key to value.
|
|
54
55
|
expand_cfg (Dict[str, list]): Config to reshape params, see `expand_torch_tensor`
|
|
55
56
|
sqeeze_cfg (Dict[str, int|None]): Config to squeeze tensors, opposite of expand.
|
|
56
|
-
|
|
57
|
+
torch_whitelist (Set[str]): Parameters to exclude from format conversion.
|
|
58
|
+
jax_whitelist (Set[str]): Parameters to exclude from format conversion.
|
|
57
59
|
strict (bool): Whether to crash on missing parameters one of the models.
|
|
58
60
|
source (str): Torch Hub or timm.
|
|
59
|
-
torch_hub_cfg (
|
|
61
|
+
torch_hub_cfg (dict): args to pass to `torch.hub.load`.
|
|
60
62
|
torch_model [torch.nn.Module]: Custom torch model
|
|
61
63
|
timm_cfg (Optional[list]): args to pass to `timm.create_model`.
|
|
62
64
|
return_torch (bool): Return both jax and torch models.
|
|
@@ -79,7 +81,7 @@ def convert_params_from_torch(
|
|
|
79
81
|
raise ValueError(
|
|
80
82
|
"The `torchhub` source is selected but `torch_hub_cfg` is None."
|
|
81
83
|
)
|
|
82
|
-
torch_model = torch.hub.load(
|
|
84
|
+
torch_model = torch.hub.load(**torch_hub_cfg)
|
|
83
85
|
case "timm":
|
|
84
86
|
if timm_cfg is None:
|
|
85
87
|
raise ValueError(
|
|
@@ -107,12 +109,20 @@ def convert_params_from_torch(
|
|
|
107
109
|
|
|
108
110
|
if param_path not in torch_params:
|
|
109
111
|
_msg = f"{param_path} ({shape}) not found in PyTorch model."
|
|
110
|
-
if strict:
|
|
112
|
+
if strict and param_path not in jax_whitelist:
|
|
111
113
|
logger.error(_msg)
|
|
112
114
|
raise AttributeError(_msg)
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if param_path in jax_whitelist:
|
|
117
|
+
p = param
|
|
118
|
+
logger.warning(
|
|
119
|
+
f"{_msg} Appending original parameters to flat param list because of `jax_whitelist`."
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
p = None
|
|
123
|
+
logger.warning(f"{_msg} Appending `None` to flat param list.")
|
|
124
|
+
|
|
125
|
+
torch_params_flat.append(p)
|
|
116
126
|
continue
|
|
117
127
|
|
|
118
128
|
logger.info(f"Converting {param_path}...")
|
|
@@ -137,7 +147,7 @@ def convert_params_from_torch(
|
|
|
137
147
|
logger.warning(
|
|
138
148
|
f"PyTorch parameters `{path}` ({param.shape}) were not converted."
|
|
139
149
|
)
|
|
140
|
-
if strict and path not in
|
|
150
|
+
if strict and path not in torch_whitelist:
|
|
141
151
|
_msg = f"The PyTorch model contains parameters ({path}) that do not have a Jax counterpart."
|
|
142
152
|
logger.error(_msg)
|
|
143
153
|
raise AttributeError(_msg)
|
|
@@ -152,10 +162,11 @@ def convert_torch_to_equinox(
|
|
|
152
162
|
replace_cfg: dict = {},
|
|
153
163
|
expand_cfg: dict = {},
|
|
154
164
|
squeeze_cfg: dict = {},
|
|
155
|
-
|
|
165
|
+
torch_whitelist: list[str] = [],
|
|
166
|
+
jax_whitelist: list[str] = [],
|
|
156
167
|
strict: bool = True,
|
|
157
168
|
source: Literal["torchhub", "timm"] = "torchhub",
|
|
158
|
-
torch_hub_cfg: Optional[
|
|
169
|
+
torch_hub_cfg: Optional[dict] = None,
|
|
159
170
|
torch_model=None,
|
|
160
171
|
timm_cfg: Optional[list] = None,
|
|
161
172
|
return_torch: bool = False,
|
|
@@ -168,10 +179,11 @@ def convert_torch_to_equinox(
|
|
|
168
179
|
replace_cfg: Dict of parameter name replacements
|
|
169
180
|
expand_cfg: Dict of dimensions to expand
|
|
170
181
|
squeeze_cfg: Dict of dimensions to squeeze
|
|
171
|
-
|
|
182
|
+
torch_whitelist: List of parameters allowed to be in PT model but not in Jax
|
|
183
|
+
jax_whitelist: List of parameters allowed to be in Jax model but not in PT
|
|
172
184
|
strict: Wether to raise an issue if not all weights are converted
|
|
173
185
|
source (str): Torch Hub or timm.
|
|
174
|
-
torch_hub_cfg:
|
|
186
|
+
torch_hub_cfg (dict): torch.hub.load config
|
|
175
187
|
torch_model [torch.nn.Module]: Custom torch model
|
|
176
188
|
timm_cfg (Optional[list]): args to pass to `timm.create_model`.
|
|
177
189
|
return_torch (bool): Return both jax and torch models.
|
|
@@ -186,7 +198,8 @@ def convert_torch_to_equinox(
|
|
|
186
198
|
replace_cfg,
|
|
187
199
|
expand_cfg,
|
|
188
200
|
squeeze_cfg,
|
|
189
|
-
|
|
201
|
+
torch_whitelist,
|
|
202
|
+
jax_whitelist,
|
|
190
203
|
strict,
|
|
191
204
|
source,
|
|
192
205
|
torch_hub_cfg,
|
|
@@ -204,7 +217,8 @@ def convert_torch_to_equinox(
|
|
|
204
217
|
replace_cfg,
|
|
205
218
|
expand_cfg,
|
|
206
219
|
squeeze_cfg,
|
|
207
|
-
|
|
220
|
+
torch_whitelist,
|
|
221
|
+
jax_whitelist,
|
|
208
222
|
strict,
|
|
209
223
|
source,
|
|
210
224
|
torch_hub_cfg,
|
|
@@ -25,7 +25,7 @@ def save_model(
|
|
|
25
25
|
path: Path,
|
|
26
26
|
model: eqx.Module,
|
|
27
27
|
model_config: dict,
|
|
28
|
-
torch_hub_cfg: list[str] =
|
|
28
|
+
torch_hub_cfg: list[str] | dict = {},
|
|
29
29
|
timm_cfg: list = [],
|
|
30
30
|
compression: bool = True,
|
|
31
31
|
):
|
|
@@ -245,11 +245,32 @@ def load_model(
|
|
|
245
245
|
return model
|
|
246
246
|
|
|
247
247
|
|
|
248
|
+
def _center_crop_square(array: jnp.ndarray) -> jnp.ndarray:
|
|
249
|
+
"""Center-crop a HxW(xC) array to a square of side min(H, W).
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
array: jnp.ndarray with shape (H, W) or (H, W, C).
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Center-cropped array with shape (M, M) or (M, M, C) where M = min(H, W).
|
|
256
|
+
"""
|
|
257
|
+
if array.ndim < 2:
|
|
258
|
+
raise ValueError("Input array must have at least 2 dimensions (H, W[, C]).")
|
|
259
|
+
h, w = array.shape[:2]
|
|
260
|
+
if h == w:
|
|
261
|
+
return array
|
|
262
|
+
m = min(h, w)
|
|
263
|
+
top = (h - m) // 2
|
|
264
|
+
left = (w - m) // 2
|
|
265
|
+
return array[top : top + m, left : left + m, ...]
|
|
266
|
+
|
|
267
|
+
|
|
248
268
|
def load_image(
|
|
249
269
|
path: str,
|
|
250
270
|
mean: Optional[list[float]] = None,
|
|
251
271
|
std: Optional[list[float]] = None,
|
|
252
272
|
size: Optional[int] = None,
|
|
273
|
+
center_crop: bool = False,
|
|
253
274
|
):
|
|
254
275
|
"""Load an image and perform minor preprocessing.
|
|
255
276
|
|
|
@@ -258,6 +279,7 @@ def load_image(
|
|
|
258
279
|
mean (list, optional): Channel mean for normalization.
|
|
259
280
|
std (list, optional): Channel std for normalization.
|
|
260
281
|
size (int, optional): Size to which resize the image.
|
|
282
|
+
center_crop (bool, optional): If True, center-crop to square prior to resizing.
|
|
261
283
|
|
|
262
284
|
Returns:
|
|
263
285
|
jnp.array: The loaded image.
|
|
@@ -275,6 +297,10 @@ def load_image(
|
|
|
275
297
|
pil_image = Image.open(image_bytes)
|
|
276
298
|
|
|
277
299
|
array = jnp.array(pil_image).astype(jnp.float32) / 255.0
|
|
300
|
+
|
|
301
|
+
if center_crop:
|
|
302
|
+
array = _center_crop_square(array)
|
|
303
|
+
|
|
278
304
|
if size is not None:
|
|
279
305
|
array = jax.image.resize(array, (size, size, 3), method="bilinear")
|
|
280
306
|
|
|
@@ -16,6 +16,70 @@ from equimo.layers.posemb import PosCNN2D, PosEmbMLPSwinv1D, PosEmbMLPSwinv2D, R
|
|
|
16
16
|
from equimo.utils import nearest_power_of_2_divisor
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def rope_rotate_half(x: jax.Array) -> jax.Array:
|
|
20
|
+
"""Rotate last-dim pairs by 90 degrees: [x0..x_{D/2-1}, x_{D/2}..x_{D-1}]
|
|
21
|
+
-> [-x_{D/2}..-x_{D-1}, x0..x_{D/2-1}]
|
|
22
|
+
"""
|
|
23
|
+
x1, x2 = jnp.split(x, 2, axis=-1)
|
|
24
|
+
return jnp.concatenate([-x2, x1], axis=-1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def rope_apply(x: jax.Array, sin: jax.Array, cos: jax.Array) -> jax.Array:
|
|
28
|
+
"""Apply RoPE to `x` using per-position sin/cos.
|
|
29
|
+
|
|
30
|
+
Shapes:
|
|
31
|
+
- x: [..., D]
|
|
32
|
+
- sin: [..., D]
|
|
33
|
+
- cos: [..., D]
|
|
34
|
+
Broadcasting across leading axes is supported.
|
|
35
|
+
"""
|
|
36
|
+
return (x * cos) + (rope_rotate_half(x) * sin)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def rope_apply_qk_last_hw(
|
|
40
|
+
q: jax.Array, k: jax.Array, sin: jax.Array, cos: jax.Array
|
|
41
|
+
) -> tuple[jax.Array, jax.Array]:
|
|
42
|
+
"""Apply RoPE to the last HW tokens of q and k (keeping any prefix unchanged).
|
|
43
|
+
|
|
44
|
+
Shapes:
|
|
45
|
+
- q, k: [H, N, D] (heads, tokens, head_dim)
|
|
46
|
+
- sin, cos: [HW, D]
|
|
47
|
+
- N = prefix + HW
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
- q_rot, k_rot: same shape as inputs.
|
|
51
|
+
"""
|
|
52
|
+
if sin.shape[-1] != q.shape[-1] or cos.shape[-1] != q.shape[-1]:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"sin/cos last dim must equal head_dim; got {sin.shape[-1]} vs {q.shape[-1]}"
|
|
55
|
+
)
|
|
56
|
+
N = q.shape[-2]
|
|
57
|
+
HW = sin.shape[-2]
|
|
58
|
+
prefix = N - HW
|
|
59
|
+
if prefix < 0:
|
|
60
|
+
raise ValueError(f"Sequence length N={N} smaller than HW={HW}.")
|
|
61
|
+
|
|
62
|
+
q_dtype, k_dtype = q.dtype, k.dtype
|
|
63
|
+
rope_dtype = sin.dtype
|
|
64
|
+
|
|
65
|
+
q = q.astype(rope_dtype)
|
|
66
|
+
k = k.astype(rope_dtype)
|
|
67
|
+
|
|
68
|
+
# Broadcast sin/cos across heads
|
|
69
|
+
sin_b = sin[None, :, :] # [1, HW, D]
|
|
70
|
+
cos_b = cos[None, :, :] # [1, HW, D]
|
|
71
|
+
|
|
72
|
+
q_prefix, q_tail = jnp.split(q, [prefix], axis=-2) # axis -2 is tokens
|
|
73
|
+
k_prefix, k_tail = jnp.split(k, [prefix], axis=-2)
|
|
74
|
+
|
|
75
|
+
q_tail = rope_apply(q_tail, sin_b, cos_b)
|
|
76
|
+
k_tail = rope_apply(k_tail, sin_b, cos_b)
|
|
77
|
+
|
|
78
|
+
q_out = jnp.concatenate([q_prefix, q_tail], axis=-2).astype(q_dtype)
|
|
79
|
+
k_out = jnp.concatenate([k_prefix, k_tail], axis=-2).astype(k_dtype)
|
|
80
|
+
return q_out, k_out
|
|
81
|
+
|
|
82
|
+
|
|
19
83
|
class Attention(eqx.Module):
|
|
20
84
|
"""Multi-head self attention module.
|
|
21
85
|
|
|
@@ -76,6 +140,7 @@ class Attention(eqx.Module):
|
|
|
76
140
|
key: PRNGKeyArray,
|
|
77
141
|
inference: Optional[bool] = None,
|
|
78
142
|
mask: Optional[Float[Array, ""]] = None,
|
|
143
|
+
rope_sincos: Optional[Tuple[jax.Array, jax.Array]] = None,
|
|
79
144
|
) -> Float[Array, "seqlen dim"]:
|
|
80
145
|
key1, key2 = jr.split(key, 2)
|
|
81
146
|
|
|
@@ -91,6 +156,15 @@ class Attention(eqx.Module):
|
|
|
91
156
|
q = jax.vmap(jax.vmap(self.q_norm))(q)
|
|
92
157
|
k = jax.vmap(jax.vmap(self.k_norm))(k)
|
|
93
158
|
|
|
159
|
+
if rope_sincos is not None:
|
|
160
|
+
sin, cos = rope_sincos # [HW, D], [HW, D]
|
|
161
|
+
if sin.shape[-1] != self.head_dim or cos.shape[-1] != self.head_dim:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"RoPE sin/cos last dim ({sin.shape[-1]}) must equal head_dim ({self.head_dim})."
|
|
164
|
+
)
|
|
165
|
+
# leave any prefix tokens untouched; rotate last HW tokens
|
|
166
|
+
q, k = rope_apply_qk_last_hw(q, k, sin, cos)
|
|
167
|
+
|
|
94
168
|
attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
|
|
95
169
|
|
|
96
170
|
if mask is not None:
|
|
@@ -255,6 +329,7 @@ class AttentionBlock(eqx.Module):
|
|
|
255
329
|
ffn_layer: eqx.Module = Mlp,
|
|
256
330
|
ffn_bias: bool = True,
|
|
257
331
|
ffn_norm: bool = False,
|
|
332
|
+
ffn_kwargs: dict = {},
|
|
258
333
|
norm_layer: eqx.Module = eqx.nn.LayerNorm,
|
|
259
334
|
post_attention_norm: bool = False,
|
|
260
335
|
init_values: float | None = None,
|
|
@@ -308,6 +383,7 @@ class AttentionBlock(eqx.Module):
|
|
|
308
383
|
bias=ffn_bias,
|
|
309
384
|
eps=eps,
|
|
310
385
|
key=key_mlp,
|
|
386
|
+
**ffn_kwargs,
|
|
311
387
|
)
|
|
312
388
|
|
|
313
389
|
self.drop_path1 = DropPathAdd(dr1)
|
|
@@ -317,6 +393,7 @@ class AttentionBlock(eqx.Module):
|
|
|
317
393
|
self,
|
|
318
394
|
x: Float[Array, "seqlen dim"],
|
|
319
395
|
key: PRNGKeyArray,
|
|
396
|
+
rope_sincos: Optional[Tuple[jax.Array, jax.Array]] = None,
|
|
320
397
|
inference: Optional[bool] = None,
|
|
321
398
|
mask: Optional[Float[Array, ""]] = None,
|
|
322
399
|
) -> Float[Array, "seqlen dim"]:
|
|
@@ -325,6 +402,11 @@ class AttentionBlock(eqx.Module):
|
|
|
325
402
|
# I chose to define extra args here rather than passing mask directly
|
|
326
403
|
# because not all attention mechanisms support masks as args
|
|
327
404
|
extra_kwargs = {"mask": mask} if mask is not None else {}
|
|
405
|
+
attn_kwargs = (
|
|
406
|
+
extra_kwargs | {"rope_sincos": rope_sincos}
|
|
407
|
+
if rope_sincos is not None
|
|
408
|
+
else extra_kwargs
|
|
409
|
+
)
|
|
328
410
|
|
|
329
411
|
x = self.drop_path1(
|
|
330
412
|
x,
|
|
@@ -334,7 +416,7 @@ class AttentionBlock(eqx.Module):
|
|
|
334
416
|
jax.vmap(self.prenorm)(x),
|
|
335
417
|
inference=inference,
|
|
336
418
|
key=key_attn,
|
|
337
|
-
**
|
|
419
|
+
**attn_kwargs,
|
|
338
420
|
)
|
|
339
421
|
)
|
|
340
422
|
),
|
|
@@ -900,10 +900,10 @@ class DSConv(eqx.Module):
|
|
|
900
900
|
key: PRNGKeyArray,
|
|
901
901
|
inference: Optional[bool] = None,
|
|
902
902
|
):
|
|
903
|
-
key_dropout, key_droppath = jr.split(key,
|
|
903
|
+
key_depth, key_point, key_dropout, key_droppath = jr.split(key, 4)
|
|
904
904
|
|
|
905
|
-
out = self.depth_conv(x)
|
|
906
|
-
out = self.point_conv(out)
|
|
905
|
+
out = self.depth_conv(x, inference=inference, key=key_depth)
|
|
906
|
+
out = self.point_conv(out, inference=inference, key=key_point)
|
|
907
907
|
|
|
908
908
|
out = self.dropout(out, inference=inference, key=key_dropout)
|
|
909
909
|
|
|
@@ -244,14 +244,8 @@ class SwiGlu(eqx.Module):
|
|
|
244
244
|
a gating mechanism where the input is transformed by two parallel paths and
|
|
245
245
|
combined multiplicatively.
|
|
246
246
|
|
|
247
|
-
The computation flow is:
|
|
248
|
-
1. Joint projection to higher dimension (w12)
|
|
249
|
-
2. Split into two paths
|
|
250
|
-
3. Apply SiLU to first path and multiply with second path
|
|
251
|
-
4. Project back to original dimension (w3)
|
|
252
|
-
|
|
253
247
|
Attributes:
|
|
254
|
-
|
|
248
|
+
w1, w2: projection layers for both paths
|
|
255
249
|
w3: Final projection layer
|
|
256
250
|
drop1: Dropout after gating
|
|
257
251
|
drop2: Dropout after final projection
|
|
@@ -260,7 +254,8 @@ class SwiGlu(eqx.Module):
|
|
|
260
254
|
[1]: https://arxiv.org/pdf/2002.05202
|
|
261
255
|
"""
|
|
262
256
|
|
|
263
|
-
|
|
257
|
+
w1: eqx.nn.Linear
|
|
258
|
+
w2: eqx.nn.Linear
|
|
264
259
|
w3: eqx.nn.Linear
|
|
265
260
|
drop1: eqx.nn.Dropout
|
|
266
261
|
drop2: eqx.nn.Dropout
|
|
@@ -273,6 +268,7 @@ class SwiGlu(eqx.Module):
|
|
|
273
268
|
out_features: int | None = None,
|
|
274
269
|
hidden_features: int | None = None,
|
|
275
270
|
dropout_rate: float = 0.0,
|
|
271
|
+
align_to: int = 8,
|
|
276
272
|
bias: bool = True,
|
|
277
273
|
**kwargs,
|
|
278
274
|
):
|
|
@@ -284,19 +280,25 @@ class SwiGlu(eqx.Module):
|
|
|
284
280
|
out_features: Number of output features (default: same as in_features)
|
|
285
281
|
hidden_features: Size of hidden dimension (default: same as in_features)
|
|
286
282
|
dropout_rate: Dropout probability (default: 0.0)
|
|
283
|
+
align_to: constrains hidden features to be a multiple of a given int (default: 8)
|
|
287
284
|
bias: Whether to include bias in linear layers (default: True)
|
|
288
285
|
**kwargs: Additional arguments
|
|
289
286
|
"""
|
|
290
|
-
key_fc1, key_fc2 = jr.split(key,
|
|
287
|
+
key_fc1, key_fc2, key_fc3 = jr.split(key, 3)
|
|
291
288
|
|
|
292
|
-
hidden_features = hidden_features or in_features
|
|
293
289
|
out_features = out_features or in_features
|
|
290
|
+
hidden_features = hidden_features or in_features
|
|
291
|
+
d = int(hidden_features * 2 / 3)
|
|
292
|
+
hidden_features = d + (-d % align_to)
|
|
294
293
|
|
|
295
|
-
self.
|
|
296
|
-
in_features,
|
|
294
|
+
self.w1 = eqx.nn.Linear(
|
|
295
|
+
in_features, hidden_features, use_bias=bias, key=key_fc1
|
|
296
|
+
)
|
|
297
|
+
self.w2 = eqx.nn.Linear(
|
|
298
|
+
in_features, hidden_features, use_bias=bias, key=key_fc2
|
|
297
299
|
)
|
|
298
300
|
self.w3 = eqx.nn.Linear(
|
|
299
|
-
hidden_features, out_features, use_bias=bias, key=
|
|
301
|
+
hidden_features, out_features, use_bias=bias, key=key_fc3
|
|
300
302
|
)
|
|
301
303
|
|
|
302
304
|
self.drop1 = eqx.nn.Dropout(dropout_rate)
|
|
@@ -310,8 +312,9 @@ class SwiGlu(eqx.Module):
|
|
|
310
312
|
) -> Float[Array, "seqlen dim"]:
|
|
311
313
|
key_dr1, key_dr2 = jr.split(key, 2)
|
|
312
314
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
x1 = jax.vmap(self.w1)(x)
|
|
316
|
+
x2 = jax.vmap(self.w2)(x)
|
|
317
|
+
|
|
315
318
|
x = self.drop1(
|
|
316
319
|
jax.nn.silu(x1) * x2,
|
|
317
320
|
inference=inference,
|
|
@@ -327,7 +330,38 @@ class SwiGlu(eqx.Module):
|
|
|
327
330
|
return x
|
|
328
331
|
|
|
329
332
|
|
|
330
|
-
class SwiGluFused(
|
|
333
|
+
class SwiGluFused(eqx.Module):
|
|
334
|
+
"""SwiGLU activation module with dropout.
|
|
335
|
+
|
|
336
|
+
This matches the implementation of Dinov2 giant at
|
|
337
|
+
https://github.com/facebookresearch/dinov2/blob/e1277af2ba9496fbadf7aec6eba56e8d882d1e35/dinov2/layers/swiglu_ffn.py#L54
|
|
338
|
+
|
|
339
|
+
Implements the SwiGLU (Swish-Gated Linear Unit) activation function with dropout,
|
|
340
|
+
as described in "GLU Variants Improve Transformer" paper [1]. The architecture uses
|
|
341
|
+
a gating mechanism where the input is transformed by two parallel paths and
|
|
342
|
+
combined multiplicatively.
|
|
343
|
+
|
|
344
|
+
The computation flow is:
|
|
345
|
+
1. Joint projection to higher dimension (w12)
|
|
346
|
+
2. Split into two paths
|
|
347
|
+
3. Apply SiLU to first path and multiply with second path
|
|
348
|
+
4. Project back to original dimension (w3)
|
|
349
|
+
|
|
350
|
+
Attributes:
|
|
351
|
+
w12: Joint projection layer for both paths
|
|
352
|
+
w3: Final projection layer
|
|
353
|
+
drop1: Dropout after gating
|
|
354
|
+
drop2: Dropout after final projection
|
|
355
|
+
|
|
356
|
+
References:
|
|
357
|
+
[1]: https://arxiv.org/pdf/2002.05202
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
w12: eqx.nn.Linear
|
|
361
|
+
w3: eqx.nn.Linear
|
|
362
|
+
drop1: eqx.nn.Dropout
|
|
363
|
+
drop2: eqx.nn.Dropout
|
|
364
|
+
|
|
331
365
|
def __init__(
|
|
332
366
|
self,
|
|
333
367
|
in_features: int,
|
|
@@ -335,27 +369,61 @@ class SwiGluFused(SwiGlu):
|
|
|
335
369
|
key: PRNGKeyArray,
|
|
336
370
|
out_features: int | None = None,
|
|
337
371
|
hidden_features: int | None = None,
|
|
338
|
-
dropout_rate: float = 0,
|
|
372
|
+
dropout_rate: float = 0.0,
|
|
339
373
|
bias: bool = True,
|
|
340
374
|
**kwargs,
|
|
341
375
|
):
|
|
342
|
-
"""
|
|
343
|
-
|
|
376
|
+
"""Initialize the SwiGLU module.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
in_features: Number of input features
|
|
380
|
+
key: PRNG key for initialization
|
|
381
|
+
out_features: Number of output features (default: same as in_features)
|
|
382
|
+
hidden_features: Size of hidden dimension (default: same as in_features)
|
|
383
|
+
dropout_rate: Dropout probability (default: 0.0)
|
|
384
|
+
bias: Whether to include bias in linear layers (default: True)
|
|
385
|
+
**kwargs: Additional arguments
|
|
344
386
|
"""
|
|
387
|
+
key_fc1, key_fc2 = jr.split(key, 2)
|
|
388
|
+
|
|
345
389
|
out_features = out_features or in_features
|
|
346
390
|
hidden_features = hidden_features or in_features
|
|
347
391
|
hidden_features = (int(hidden_features * 2 / 3) + 7) // 8 * 8
|
|
348
392
|
|
|
349
|
-
|
|
350
|
-
in_features,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
hidden_features=
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
393
|
+
self.w12 = eqx.nn.Linear(
|
|
394
|
+
in_features, 2 * hidden_features, use_bias=bias, key=key_fc1
|
|
395
|
+
)
|
|
396
|
+
self.w3 = eqx.nn.Linear(
|
|
397
|
+
hidden_features, out_features, use_bias=bias, key=key_fc2
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
self.drop1 = eqx.nn.Dropout(dropout_rate)
|
|
401
|
+
self.drop2 = eqx.nn.Dropout(dropout_rate)
|
|
402
|
+
|
|
403
|
+
def __call__(
|
|
404
|
+
self,
|
|
405
|
+
x: Float[Array, "seqlen dim"],
|
|
406
|
+
key: PRNGKeyArray,
|
|
407
|
+
inference: Optional[bool] = None,
|
|
408
|
+
) -> Float[Array, "seqlen dim"]:
|
|
409
|
+
key_dr1, key_dr2 = jr.split(key, 2)
|
|
410
|
+
|
|
411
|
+
x12 = jax.vmap(self.w12)(x)
|
|
412
|
+
x1, x2 = jnp.split(x12, 2, axis=-1)
|
|
413
|
+
x = self.drop1(
|
|
414
|
+
jax.nn.silu(x1) * x2,
|
|
415
|
+
inference=inference,
|
|
416
|
+
key=key_dr1,
|
|
357
417
|
)
|
|
358
418
|
|
|
419
|
+
x = self.drop2(
|
|
420
|
+
jax.vmap(self.w3)(x),
|
|
421
|
+
inference=inference,
|
|
422
|
+
key=key_dr2,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
return x
|
|
426
|
+
|
|
359
427
|
|
|
360
428
|
def get_ffn(module: str | eqx.Module) -> eqx.Module:
|
|
361
429
|
"""Get an `eqx.Module` from its common name.
|