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.
Files changed (45) hide show
  1. {equimo-0.4.0a15/src/Equimo.egg-info → equimo-0.4.2}/PKG-INFO +3 -2
  2. {equimo-0.4.0a15 → equimo-0.4.2}/README.md +1 -0
  3. {equimo-0.4.0a15 → equimo-0.4.2}/pyproject.toml +2 -2
  4. {equimo-0.4.0a15 → equimo-0.4.2/src/Equimo.egg-info}/PKG-INFO +3 -2
  5. equimo-0.4.2/src/equimo/__init__.py +1 -0
  6. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/conversion/utils.py +30 -16
  7. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/io.py +27 -1
  8. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/attention.py +83 -1
  9. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/convolution.py +3 -3
  10. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/ffn.py +95 -27
  11. equimo-0.4.2/src/equimo/layers/posemb.py +753 -0
  12. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/reduceformer.py +7 -6
  13. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/vit.py +94 -113
  14. {equimo-0.4.0a15 → equimo-0.4.2}/tests/test_models.py +11 -0
  15. equimo-0.4.0a15/src/equimo/__init__.py +0 -1
  16. equimo-0.4.0a15/src/equimo/layers/posemb.py +0 -416
  17. {equimo-0.4.0a15 → equimo-0.4.2}/LICENSE.md +0 -0
  18. {equimo-0.4.0a15 → equimo-0.4.2}/setup.cfg +0 -0
  19. {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/SOURCES.txt +0 -0
  20. {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/dependency_links.txt +0 -0
  21. {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/requires.txt +0 -0
  22. {equimo-0.4.0a15 → equimo-0.4.2}/src/Equimo.egg-info/top_level.txt +0 -0
  23. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/conversion/__init__.py +0 -0
  24. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/experimental/__init__.py +0 -0
  25. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/experimental/text.py +0 -0
  26. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/__init__.py +0 -0
  27. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/activation.py +0 -0
  28. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/downsample.py +0 -0
  29. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/dropout.py +0 -0
  30. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/generic.py +0 -0
  31. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/mamba.py +0 -0
  32. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/norm.py +0 -0
  33. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/patch.py +0 -0
  34. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/sharing.py +0 -0
  35. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/layers/squeeze_excite.py +0 -0
  36. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/__init__.py +0 -0
  37. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/emamodel.py +0 -0
  38. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/fastervit.py +0 -0
  39. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/lowformer.py +0 -0
  40. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/mlla.py +0 -0
  41. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/partialformer.py +0 -0
  42. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/shvit.py +0 -0
  43. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/models/vssd.py +0 -0
  44. {equimo-0.4.0a15 → equimo-0.4.2}/src/equimo/ops/scan.py +0 -0
  45. {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.0a15
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.10
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.0-alpha.15"
3
+ version = "0.4.2"
4
4
  description = "Implementation of popular vision models in Jax"
5
5
  readme = "README.md"
6
- requires-python = ">=3.10"
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.0a15
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.10
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
- whitelist: list[str],
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[list[str]] = None,
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 (Tuple[str]): Arguments passed to `torch.hub.load()`.
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
- whitelist (Set[str]): Parameters to exclude from format conversion.
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 (Optional[list]): args to pass to `torch.hub.load`.
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(*torch_hub_cfg)
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
- logger.warning(f"{_msg} Appending `None` to flat param list.")
115
- torch_params_flat.append(None)
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 whitelist:
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
- whitelist: list[str] = [],
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[list[str]] = None,
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
- whitelist: List of parameters to keep from JAX model
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: [repo, model_name] for torch.hub.load
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
- whitelist,
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
- whitelist,
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
- **extra_kwargs,
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, 2)
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
- w12: Joint projection layer for both paths
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
- w12: eqx.nn.Linear
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, 2)
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.w12 = eqx.nn.Linear(
296
- in_features, 2 * hidden_features, use_bias=bias, key=key_fc1
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=key_fc2
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
- x12 = jax.vmap(self.w12)(x)
314
- x1, x2 = jnp.split(x12, 2, axis=-1)
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(SwiGlu):
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
- """This matches the implementation of Dinov2 giant at
343
- https://github.com/facebookresearch/dinov2/blob/e1277af2ba9496fbadf7aec6eba56e8d882d1e35/dinov2/layers/swiglu_ffn.py#L54
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
- super().__init__(
350
- in_features,
351
- key=key,
352
- out_features=out_features,
353
- hidden_features=hidden_features,
354
- dropout_rate=dropout_rate,
355
- bias=bias,
356
- **kwargs,
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.