Equimo 0.2.2__tar.gz → 0.2.3__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 (42) hide show
  1. {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/PKG-INFO +23 -10
  2. {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/SOURCES.txt +2 -0
  3. {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/requires.txt +2 -0
  4. {equimo-0.2.2 → equimo-0.2.3}/PKG-INFO +23 -10
  5. {equimo-0.2.2 → equimo-0.2.3}/README.md +20 -9
  6. equimo-0.2.3/equimo/__init__.py +1 -0
  7. equimo-0.2.3/equimo/converters/siglip2.py +105 -0
  8. {equimo-0.2.2 → equimo-0.2.3}/equimo/converters/utils.py +72 -19
  9. {equimo-0.2.2 → equimo-0.2.3}/equimo/io.py +20 -4
  10. equimo-0.2.3/equimo/layers/activation.py +33 -0
  11. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/vit.py +4 -2
  12. {equimo-0.2.2 → equimo-0.2.3}/pyproject.toml +3 -1
  13. equimo-0.2.2/equimo/layers/__init__.py +0 -0
  14. {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/dependency_links.txt +0 -0
  15. {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/top_level.txt +0 -0
  16. {equimo-0.2.2 → equimo-0.2.3}/LICENSE.md +0 -0
  17. {equimo-0.2.2 → equimo-0.2.3}/equimo/converters/__init__.py +0 -0
  18. {equimo-0.2.2 → equimo-0.2.3}/equimo/converters/dinov2.py +0 -0
  19. {equimo-0.2.2/equimo → equimo-0.2.3/equimo/layers}/__init__.py +0 -0
  20. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/attention.py +0 -0
  21. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/convolution.py +0 -0
  22. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/downsample.py +0 -0
  23. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/dropout.py +0 -0
  24. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/ffn.py +0 -0
  25. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/generic.py +0 -0
  26. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/mamba.py +0 -0
  27. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/norm.py +0 -0
  28. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/patch.py +0 -0
  29. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/posemb.py +0 -0
  30. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/sharing.py +0 -0
  31. {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/squeeze_excite.py +0 -0
  32. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/__init__.py +0 -0
  33. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/emamodel.py +0 -0
  34. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/fastervit.py +0 -0
  35. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/mlla.py +0 -0
  36. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/partialformer.py +0 -0
  37. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/shvit.py +0 -0
  38. {equimo-0.2.2 → equimo-0.2.3}/equimo/models/vssd.py +0 -0
  39. {equimo-0.2.2 → equimo-0.2.3}/equimo/ops/scan.py +0 -0
  40. {equimo-0.2.2 → equimo-0.2.3}/equimo/utils.py +0 -0
  41. {equimo-0.2.2 → equimo-0.2.3}/setup.cfg +0 -0
  42. {equimo-0.2.2 → equimo-0.2.3}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Equimo
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -20,8 +20,10 @@ Requires-Dist: jaxlib>=0.4.25
20
20
  Requires-Dist: loguru>=0.7.3
21
21
  Requires-Dist: lz4>=4.4.3
22
22
  Requires-Dist: requests>=2.32.3
23
+ Requires-Dist: semver>=3.0.4
23
24
  Provides-Extra: torch
24
25
  Requires-Dist: torch>=2.6.0; extra == "torch"
26
+ Requires-Dist: timm>=1.0.15; extra == "torch"
25
27
 
26
28
  # Equimo: Modern Vision Models in JAX/Equinox
27
29
 
@@ -54,6 +56,8 @@ pip install -e .
54
56
 
55
57
  ## Implemented Models
56
58
 
59
+ Beyond normal ViT (e.g., dinov2 or siglip), equimo proposes other SotA architectures:
60
+
57
61
  | Model | Paper | Year | Status |
58
62
  | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | --------- |
59
63
  | FasterViT | [FasterViT: Fast Vision Transformers with Hierarchical Attention](https://arxiv.org/abs/2306.06189) | 2023 | ✅ |
@@ -139,19 +143,28 @@ model = load_model(cls="vit", path=Path("path/to/model.tar.lz4"))
139
143
  model = load_model(cls="vit", path=Path("path/to/model/"))
140
144
  ```
141
145
 
146
+ Parameters passed to models can be overridden such as:
147
+
148
+ ```python
149
+ model = load_model(
150
+ cls="vit",
151
+ identifier="siglip2_vitb16_256",
152
+ dynamic_img_size=True, # passed to the VisionTransformer class
153
+ )
154
+ ```
155
+
142
156
  #### List of pretrained models
143
157
 
144
- Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) have been ported, therefore the following
145
- identifiers are available:
158
+ Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) and SigLIP2 have been ported.
159
+
160
+ Model identifiers allows downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
146
161
 
147
- - `dinov2_vitb14.tar.lz4`
148
- - `dinov2_vitb14_reg.tar.lz4`
149
- - `dinov2_vitl14.tar.lz4`
150
- - `dinov2_vitl14_reg.tar.lz4`
151
- - `dinov2_vits14.tar.lz4`
152
- - `dinov2_vits14_reg.tar.lz4`
162
+ Identifiers are filenames without the extensions, such as:
153
163
 
154
- (`giant` version coming soon)
164
+ - `dinov2_vitb14`
165
+ - `dinov2_vits14_reg`
166
+ - `siglip2_vitl16_512`
167
+ - `siglip2_vitso400m16_384`
155
168
 
156
169
  ## Contributing
157
170
 
@@ -11,8 +11,10 @@ equimo/io.py
11
11
  equimo/utils.py
12
12
  equimo/converters/__init__.py
13
13
  equimo/converters/dinov2.py
14
+ equimo/converters/siglip2.py
14
15
  equimo/converters/utils.py
15
16
  equimo/layers/__init__.py
17
+ equimo/layers/activation.py
16
18
  equimo/layers/attention.py
17
19
  equimo/layers/convolution.py
18
20
  equimo/layers/downsample.py
@@ -5,6 +5,8 @@ jaxlib>=0.4.25
5
5
  loguru>=0.7.3
6
6
  lz4>=4.4.3
7
7
  requests>=2.32.3
8
+ semver>=3.0.4
8
9
 
9
10
  [torch]
10
11
  torch>=2.6.0
12
+ timm>=1.0.15
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Equimo
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -20,8 +20,10 @@ Requires-Dist: jaxlib>=0.4.25
20
20
  Requires-Dist: loguru>=0.7.3
21
21
  Requires-Dist: lz4>=4.4.3
22
22
  Requires-Dist: requests>=2.32.3
23
+ Requires-Dist: semver>=3.0.4
23
24
  Provides-Extra: torch
24
25
  Requires-Dist: torch>=2.6.0; extra == "torch"
26
+ Requires-Dist: timm>=1.0.15; extra == "torch"
25
27
 
26
28
  # Equimo: Modern Vision Models in JAX/Equinox
27
29
 
@@ -54,6 +56,8 @@ pip install -e .
54
56
 
55
57
  ## Implemented Models
56
58
 
59
+ Beyond normal ViT (e.g., dinov2 or siglip), equimo proposes other SotA architectures:
60
+
57
61
  | Model | Paper | Year | Status |
58
62
  | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | --------- |
59
63
  | FasterViT | [FasterViT: Fast Vision Transformers with Hierarchical Attention](https://arxiv.org/abs/2306.06189) | 2023 | ✅ |
@@ -139,19 +143,28 @@ model = load_model(cls="vit", path=Path("path/to/model.tar.lz4"))
139
143
  model = load_model(cls="vit", path=Path("path/to/model/"))
140
144
  ```
141
145
 
146
+ Parameters passed to models can be overridden such as:
147
+
148
+ ```python
149
+ model = load_model(
150
+ cls="vit",
151
+ identifier="siglip2_vitb16_256",
152
+ dynamic_img_size=True, # passed to the VisionTransformer class
153
+ )
154
+ ```
155
+
142
156
  #### List of pretrained models
143
157
 
144
- Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) have been ported, therefore the following
145
- identifiers are available:
158
+ Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) and SigLIP2 have been ported.
159
+
160
+ Model identifiers allows downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
146
161
 
147
- - `dinov2_vitb14.tar.lz4`
148
- - `dinov2_vitb14_reg.tar.lz4`
149
- - `dinov2_vitl14.tar.lz4`
150
- - `dinov2_vitl14_reg.tar.lz4`
151
- - `dinov2_vits14.tar.lz4`
152
- - `dinov2_vits14_reg.tar.lz4`
162
+ Identifiers are filenames without the extensions, such as:
153
163
 
154
- (`giant` version coming soon)
164
+ - `dinov2_vitb14`
165
+ - `dinov2_vits14_reg`
166
+ - `siglip2_vitl16_512`
167
+ - `siglip2_vitso400m16_384`
155
168
 
156
169
  ## Contributing
157
170
 
@@ -29,6 +29,8 @@ pip install -e .
29
29
 
30
30
  ## Implemented Models
31
31
 
32
+ Beyond normal ViT (e.g., dinov2 or siglip), equimo proposes other SotA architectures:
33
+
32
34
  | Model | Paper | Year | Status |
33
35
  | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | --------- |
34
36
  | FasterViT | [FasterViT: Fast Vision Transformers with Hierarchical Attention](https://arxiv.org/abs/2306.06189) | 2023 | ✅ |
@@ -114,19 +116,28 @@ model = load_model(cls="vit", path=Path("path/to/model.tar.lz4"))
114
116
  model = load_model(cls="vit", path=Path("path/to/model/"))
115
117
  ```
116
118
 
119
+ Parameters passed to models can be overridden such as:
120
+
121
+ ```python
122
+ model = load_model(
123
+ cls="vit",
124
+ identifier="siglip2_vitb16_256",
125
+ dynamic_img_size=True, # passed to the VisionTransformer class
126
+ )
127
+ ```
128
+
117
129
  #### List of pretrained models
118
130
 
119
- Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) have been ported, therefore the following
120
- identifiers are available:
131
+ Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) and SigLIP2 have been ported.
132
+
133
+ Model identifiers allows downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
121
134
 
122
- - `dinov2_vitb14.tar.lz4`
123
- - `dinov2_vitb14_reg.tar.lz4`
124
- - `dinov2_vitl14.tar.lz4`
125
- - `dinov2_vitl14_reg.tar.lz4`
126
- - `dinov2_vits14.tar.lz4`
127
- - `dinov2_vits14_reg.tar.lz4`
135
+ Identifiers are filenames without the extensions, such as:
128
136
 
129
- (`giant` version coming soon)
137
+ - `dinov2_vitb14`
138
+ - `dinov2_vits14_reg`
139
+ - `siglip2_vitl16_512`
140
+ - `siglip2_vitso400m16_384`
130
141
 
131
142
  ## Contributing
132
143
 
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"
@@ -0,0 +1,105 @@
1
+ from pathlib import Path
2
+
3
+ import jax
4
+ import jax.numpy as jnp
5
+ import numpy as np
6
+
7
+ import equimo.models as em
8
+ from equimo.converters.utils import convert_torch_to_equinox
9
+ from equimo.io import load_model, save_model
10
+
11
+
12
+ def compare(j, t) -> float:
13
+ j = np.array(j)
14
+ t = t.squeeze().detach().numpy()
15
+ return float(np.mean(np.abs(j - t)))
16
+
17
+
18
+ def main():
19
+ try:
20
+ import torch
21
+ except:
22
+ raise ImportError("`torch` not available")
23
+
24
+ key = jax.random.PRNGKey(42)
25
+ siglip2_config = {
26
+ "img_size": 384,
27
+ "in_channels": 3,
28
+ "dim": 1536,
29
+ "patch_size": 16,
30
+ "num_heads": [16],
31
+ "depths": [40],
32
+ "mlp_ratio": 4,
33
+ "num_classes": 0,
34
+ "use_mask_token": False,
35
+ "reg_tokens": 0,
36
+ "class_token": False,
37
+ "no_embed_class": True,
38
+ "init_values": None,
39
+ "eps": 1e-6,
40
+ "dynamic_img_size": False,
41
+ # "act_layer": "exactgelu",
42
+ "act_layer": "gelu",
43
+ }
44
+
45
+ siglip2 = em.VisionTransformer(
46
+ **siglip2_config,
47
+ key=key,
48
+ )
49
+
50
+ timm_cfg = ["vit_giantopt_patch16_siglip_gap_384.v2_webli", True]
51
+ _n = timm_cfg[0].split("_")
52
+ mname = f"siglip2_vitgiantopt{_n[2][-2:]}_{_n[-2][:3]}"
53
+ # mname = f"siglip2_vit{_n[1][0]}{_n[2][-2:]}_{_n[-2][:3]}"
54
+
55
+ print(f"Converting {mname}...")
56
+
57
+ replace_cfg = {
58
+ "blocks.0.blocks": "blocks",
59
+ ".prenorm.": ".norm1.",
60
+ ".norm.": ".norm2.",
61
+ }
62
+ expand_cfg = {"patch_embed.proj.bias": ["after", 2]}
63
+ squeeze_cfg = {
64
+ "pos_embed": 0,
65
+ "cls_token": 0,
66
+ "register_tokens": 0,
67
+ }
68
+ whitelist = []
69
+
70
+ siglip2, torch_model = convert_torch_to_equinox(
71
+ siglip2,
72
+ replace_cfg,
73
+ expand_cfg,
74
+ squeeze_cfg,
75
+ whitelist,
76
+ strict=True,
77
+ source="timm",
78
+ timm_cfg=timm_cfg,
79
+ return_torch=True,
80
+ )
81
+
82
+ arr = np.random.randn(3, siglip2_config["img_size"], siglip2_config["img_size"])
83
+ jax_arr = jnp.array(arr)
84
+ torch_arr = torch.tensor(arr).unsqueeze(0).float()
85
+
86
+ assert (
87
+ compare(
88
+ jax.vmap(siglip2.norm)(siglip2.features(jax_arr, key)),
89
+ torch_model.forward_features(torch_arr),
90
+ )
91
+ < 1e-5
92
+ )
93
+
94
+ save_model(
95
+ Path(f"~/.cache/equimo/{mname}").expanduser(),
96
+ siglip2,
97
+ siglip2_config,
98
+ timm_cfg=timm_cfg,
99
+ compression=True,
100
+ )
101
+
102
+ _ = load_model(
103
+ cls="vit",
104
+ path=Path(f"~/.cache/equimo/{mname}.tar.lz4").expanduser(),
105
+ )
@@ -1,5 +1,5 @@
1
1
  import re
2
- from typing import Dict, Tuple
2
+ from typing import Any, Dict, Literal, Optional, Tuple
3
3
 
4
4
  import equinox as eqx
5
5
  import jax
@@ -33,12 +33,15 @@ def expand_torch_tensor(tensor, pos: str, n: int):
33
33
 
34
34
  def convert_params_from_torch_hub(
35
35
  jax_model: eqx.Module,
36
- torch_hub_cfg: list[str],
37
36
  replace_cfg: Dict[str, str],
38
37
  expand_cfg: Dict[str, list],
39
38
  squeeze_cfg: Dict[str, int | None],
40
39
  whitelist: list[str],
41
40
  strict: bool = True,
41
+ source: Literal["torchhub", "timm"] = "torchhub",
42
+ torch_hub_cfg: Optional[list[str]] = None,
43
+ timm_cfg: Optional[list] = None,
44
+ return_torch: bool = False,
42
45
  ):
43
46
  """
44
47
  Load weights from a torch hub model into an Equinox module.
@@ -46,16 +49,37 @@ def convert_params_from_torch_hub(
46
49
  Args:
47
50
  jax_model (eqx.Module): A preexisting Jax model corresponding to the checkpoint to download.
48
51
  torch_hub_cfg (Tuple[str]): Arguments passed to `torch.hub.load()`.
49
- transpose_whitelist (Set[str]): Parameters to exclude from format conversion.
52
+ replace_cfg (Dict[str, str]): Rename parameters from key to value.
53
+ expand_cfg (Dict[str, list]): Config to reshape params, see `expand_torch_tensor`
54
+ sqeeze_cfg (Dict[str, int|None]): Config to squeeze tensors, opposite of expand.
55
+ whitelist (Set[str]): Parameters to exclude from format conversion.
50
56
  strict (bool): Whether to crash on missing parameters one of the models.
57
+ source (str): Torch Hub or timm.
58
+ torch_hub_cfg (Optional[list]): args to pass to `torch.hub.load`.
59
+ timm_cfg (Optional[list]): args to pass to `timm.create_model`.
60
+ return_torch (bool): Return both jax and torch models.
51
61
  """
52
62
  try:
63
+ import timm
53
64
  import torch
54
65
  except:
55
66
  raise ImportError("`torch` not available")
56
67
 
57
- # Load the pytorch model from torch hub
58
- torch_model = torch.hub.load(*torch_hub_cfg)
68
+ # Load the pytorch model
69
+ match source:
70
+ case "torchhub":
71
+ if torch_hub_cfg is None:
72
+ raise ValueError(
73
+ "The `torchhub` source is selected but `torch_hub_cfg` is None."
74
+ )
75
+ torch_model = torch.hub.load(*torch_hub_cfg)
76
+ case "timm":
77
+ if timm_cfg is None:
78
+ raise ValueError(
79
+ "The `timm` source is selected but `timm_cfg` is None."
80
+ )
81
+ torch_model = timm.create_model(*timm_cfg)
82
+
59
83
  torch_params = dict(torch_model.named_parameters())
60
84
 
61
85
  # Extract the parameters from the defined Jax model
@@ -111,42 +135,71 @@ def convert_params_from_torch_hub(
111
135
  logger.error(_msg)
112
136
  raise AttributeError(_msg)
113
137
 
138
+ if return_torch:
139
+ return loaded_params, torch_model
114
140
  return loaded_params
115
141
 
116
142
 
117
143
  def convert_torch_to_equinox(
118
144
  jax_model: eqx.Module,
119
- torch_hub_cfg: list[str],
120
145
  replace_cfg: dict = {},
121
146
  expand_cfg: dict = {},
122
147
  squeeze_cfg: dict = {},
123
148
  whitelist: list[str] = [],
124
149
  strict: bool = True,
125
- ) -> eqx.Module:
150
+ source: Literal["torchhub", "timm"] = "torchhub",
151
+ torch_hub_cfg: Optional[list[str]] = None,
152
+ timm_cfg: Optional[list] = None,
153
+ return_torch: bool = False,
154
+ ) -> eqx.Module | Tuple[eqx.Module, Any]:
126
155
  """
127
156
  Convert a PyTorch model from torch.hub to Equinox format.
128
157
 
129
158
  Args:
130
159
  jax_model: The Equinox model
131
- torch_hub_cfg: [repo, model_name] for torch.hub.load
132
160
  replace_cfg: Dict of parameter name replacements
133
161
  expand_cfg: Dict of dimensions to expand
134
162
  squeeze_cfg: Dict of dimensions to squeeze
135
163
  whitelist: List of parameters to keep from JAX model
136
164
  strict: Wether to raise an issue if not all weights are converted
165
+ source (str): Torch Hub or timm.
166
+ torch_hub_cfg: [repo, model_name] for torch.hub.load
167
+ timm_cfg (Optional[list]): args to pass to `timm.create_model`.
168
+ return_torch (bool): Return both jax and torch models.
137
169
 
138
170
  Returns:
139
171
  eqx.Module: Converted Equinox model in inference mode
140
172
  """
141
173
  dynamic, static = eqx.partition(jax_model, eqx.is_array)
142
- converted_params = convert_params_from_torch_hub(
143
- dynamic,
144
- torch_hub_cfg,
145
- replace_cfg,
146
- expand_cfg,
147
- squeeze_cfg,
148
- whitelist,
149
- strict,
150
- )
151
-
152
- return eqx.nn.inference_mode(eqx.combine(converted_params, static), value=True)
174
+ if return_torch:
175
+ converted_params, torch_model = convert_params_from_torch_hub(
176
+ dynamic,
177
+ replace_cfg,
178
+ expand_cfg,
179
+ squeeze_cfg,
180
+ whitelist,
181
+ strict,
182
+ source,
183
+ torch_hub_cfg,
184
+ timm_cfg,
185
+ return_torch,
186
+ )
187
+
188
+ return eqx.nn.inference_mode(
189
+ eqx.combine(converted_params, static), value=True
190
+ ), torch_model.eval()
191
+ else:
192
+ converted_params = convert_params_from_torch_hub(
193
+ dynamic,
194
+ replace_cfg,
195
+ expand_cfg,
196
+ squeeze_cfg,
197
+ whitelist,
198
+ strict,
199
+ source,
200
+ torch_hub_cfg,
201
+ timm_cfg,
202
+ return_torch,
203
+ )
204
+
205
+ return eqx.nn.inference_mode(eqx.combine(converted_params, static), value=True)
@@ -8,8 +8,10 @@ import jax
8
8
  import lz4.frame
9
9
  import requests
10
10
  from loguru import logger
11
+ from semver import Version
11
12
 
12
13
  import equimo.models as em
14
+ from equimo import __version__
13
15
 
14
16
  DEFAULT_REPOSITORY_URL = (
15
17
  "https://huggingface.co/poiretclement/equimo/resolve/main/models/default"
@@ -20,7 +22,8 @@ def save_model(
20
22
  path: Path,
21
23
  model: eqx.Module,
22
24
  model_config: dict,
23
- torch_hub_cfg: list[str],
25
+ torch_hub_cfg: list[str] = [],
26
+ timm_cfg: list = [],
24
27
  compression: bool = True,
25
28
  ):
26
29
  """Save an Equinox model with its configuration and metadata to disk.
@@ -31,6 +34,7 @@ def save_model(
31
34
  model (eqx.Module): The Equinox model to be saved.
32
35
  model_config (dict): Configuration dictionary containing model hyperparameters.
33
36
  torch_hub_cfg (list[str]): List of torch hub configuration parameters.
37
+ timm_cfg (list[str]): List of timm configuration parameters.
34
38
  compression (bool, optional): Whether to compress the saved model using LZ4.
35
39
  Defaults to True.
36
40
 
@@ -46,8 +50,10 @@ def save_model(
46
50
  metadata = {
47
51
  "model_config": model_config,
48
52
  "torch_hub_cfg": torch_hub_cfg,
53
+ "timm": timm_cfg,
49
54
  "jax_version": jax.__version__,
50
55
  "equinox_version": eqx.__version__,
56
+ "equimo_version": __version__,
51
57
  }
52
58
 
53
59
  logger.debug(f"Metadata: {metadata}")
@@ -100,8 +106,9 @@ def download(identifier: str, repository: str) -> Path:
100
106
 
101
107
  logger.info(f"Downloading {identifier}...")
102
108
 
103
- url = f"{repository}/{identifier}.tar.lz4"
104
- path = Path(f"~/.cache/equimo/{identifier}.tar.lz4").expanduser()
109
+ model = identifier.split("_")[0]
110
+ url = f"{repository}/{model}/{identifier}.tar.lz4"
111
+ path = Path(f"~/.cache/equimo/{model}/{identifier}.tar.lz4").expanduser()
105
112
  path.parent.mkdir(parents=True, exist_ok=True)
106
113
 
107
114
  if path.exists():
@@ -123,6 +130,7 @@ def load_model(
123
130
  identifier: str | None = None,
124
131
  path: Path | None = None,
125
132
  repository: str = DEFAULT_REPOSITORY_URL,
133
+ **model_kwargs,
126
134
  ) -> eqx.Module:
127
135
  """Load an Equinox model from either a local path or remote repository.
128
136
 
@@ -135,6 +143,7 @@ def load_model(
135
143
  Mutually exclusive with identifier. Defaults to None.
136
144
  repository (str, optional): Base URL for model download.
137
145
  Defaults to DEFAULT_REPOSITORY_URL.
146
+ model_kwargs: kwargs passed to model instanciation. Overrides metadatas.
138
147
 
139
148
  Returns:
140
149
  eqx.Module: Loaded and initialized model with weights.
@@ -189,6 +198,12 @@ def load_model(
189
198
 
190
199
  logger.debug(f"Metadata: {metadata}")
191
200
 
201
+ model_eqm_version = metadata.get("equimo_version", "0.2.0")
202
+ if Version.parse(model_eqm_version) > Version.parse(__version__):
203
+ logger.warning(
204
+ f"The model you are importing was packaged with equimo v{model_eqm_version}, but you have equimo v{__version__}. You may face unexpected errors. Please consider updating equimo."
205
+ )
206
+
192
207
  # Class resolution
193
208
  match cls:
194
209
  case "vit":
@@ -207,7 +222,8 @@ def load_model(
207
222
  raise ValueError(f"Unknown model class: {cls}")
208
223
 
209
224
  # Reconstruct model skeleton
210
- model = model_cls(**metadata["model_config"], key=jax.random.PRNGKey(42))
225
+ kwargs = metadata["model_config"] | model_kwargs
226
+ model = model_cls(**kwargs, key=jax.random.PRNGKey(42))
211
227
 
212
228
  # Load weights
213
229
  model = eqx.tree_deserialise_leaves(load_path / "weights.eqx", model)
@@ -0,0 +1,33 @@
1
+ from typing import Callable
2
+ import jax
3
+ from functools import partial
4
+
5
+
6
+ def get_act(activation: str | Callable) -> Callable:
7
+ """Get an activation function from its common name.
8
+
9
+ This is necessary because configs have to be stringified and stored as
10
+ json files to allow (de)serialization.
11
+ """
12
+ if not isinstance(activation, str):
13
+ return activation
14
+
15
+ match activation:
16
+ case "relu":
17
+ return jax.nn.relu
18
+ case "gelu":
19
+ return jax.nn.gelu
20
+ case "exactgelu":
21
+ return partial(jax.nn.gelu, approximate=False)
22
+ case "silu":
23
+ return jax.nn.silu
24
+ case "elu":
25
+ return jax.nn.elu
26
+ case "sigmoid":
27
+ return jax.nn.sigmoid
28
+ case "hard_sigmoid":
29
+ return jax.nn.hard_sigmoid
30
+ case "softmax":
31
+ return jax.nn.softmax
32
+ case _:
33
+ raise ValueError(f"Got an unknown activation string: {activation}")
@@ -8,6 +8,7 @@ import jax.random as jr
8
8
  from einops import rearrange
9
9
  from jaxtyping import Array, Float, Int, PRNGKeyArray
10
10
 
11
+ from equimo.layers.activation import get_act
11
12
  from equimo.layers.attention import (
12
13
  Attention,
13
14
  AttentionBlock,
@@ -190,7 +191,7 @@ class VisionTransformer(eqx.Module):
190
191
  qk_norm: bool = False,
191
192
  attn_drop: float = 0.0,
192
193
  proj_drop: float = 0.0,
193
- act_layer: Callable = jax.nn.gelu,
194
+ act_layer: Callable | str = jax.nn.gelu,
194
195
  attn_layer: str | eqx.Module = Attention,
195
196
  ffn_layer: str | eqx.Module = Mlp,
196
197
  ffn_bias: bool = True,
@@ -222,6 +223,7 @@ class VisionTransformer(eqx.Module):
222
223
  attn_layer = get_attention(attn_layer)
223
224
  ffn_layer = get_ffn(ffn_layer)
224
225
  norm_layer = get_norm(norm_layer)
226
+ act_layer = get_act(act_layer)
225
227
 
226
228
  self.patch_embed = PatchEmbedding(
227
229
  in_channels,
@@ -234,7 +236,7 @@ class VisionTransformer(eqx.Module):
234
236
  key=key_patchemb,
235
237
  )
236
238
  self.num_patches = self.patch_embed.num_patches
237
- self.cls_token = jr.normal(key_cls, (1, dim))
239
+ self.cls_token = jr.normal(key_cls, (1, dim)) if class_token else None
238
240
  self.reg_tokens = (
239
241
  jr.normal(key_reg, (reg_tokens, dim)) if reg_tokens > 0 else None
240
242
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Equimo"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Implementation of popular vision models in Jax"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -12,6 +12,7 @@ dependencies = [
12
12
  "loguru>=0.7.3",
13
13
  "lz4>=4.4.3",
14
14
  "requests>=2.32.3",
15
+ "semver>=3.0.4",
15
16
  ]
16
17
  classifiers = [
17
18
  "Development Status :: 4 - Beta",
@@ -32,6 +33,7 @@ classifiers = [
32
33
  [project.optional-dependencies]
33
34
  torch = [
34
35
  "torch>=2.6.0",
36
+ "timm>=1.0.15",
35
37
  ]
36
38
 
37
39
  [dependency-groups]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes