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.
- {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/PKG-INFO +23 -10
- {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/SOURCES.txt +2 -0
- {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/requires.txt +2 -0
- {equimo-0.2.2 → equimo-0.2.3}/PKG-INFO +23 -10
- {equimo-0.2.2 → equimo-0.2.3}/README.md +20 -9
- equimo-0.2.3/equimo/__init__.py +1 -0
- equimo-0.2.3/equimo/converters/siglip2.py +105 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/converters/utils.py +72 -19
- {equimo-0.2.2 → equimo-0.2.3}/equimo/io.py +20 -4
- equimo-0.2.3/equimo/layers/activation.py +33 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/vit.py +4 -2
- {equimo-0.2.2 → equimo-0.2.3}/pyproject.toml +3 -1
- equimo-0.2.2/equimo/layers/__init__.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/dependency_links.txt +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/Equimo.egg-info/top_level.txt +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/LICENSE.md +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/converters/__init__.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/converters/dinov2.py +0 -0
- {equimo-0.2.2/equimo → equimo-0.2.3/equimo/layers}/__init__.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/attention.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/convolution.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/downsample.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/dropout.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/ffn.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/generic.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/mamba.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/norm.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/patch.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/posemb.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/sharing.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/layers/squeeze_excite.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/__init__.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/emamodel.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/fastervit.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/mlla.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/partialformer.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/shvit.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/models/vssd.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/ops/scan.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/equimo/utils.py +0 -0
- {equimo-0.2.2 → equimo-0.2.3}/setup.cfg +0 -0
- {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.
|
|
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
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: Equimo
|
|
3
|
-
Version: 0.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
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|