Equimo 0.2.3__tar.gz → 0.3.0__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.3 → equimo-0.3.0}/Equimo.egg-info/PKG-INFO +86 -3
  2. {equimo-0.2.3 → equimo-0.3.0}/Equimo.egg-info/SOURCES.txt +0 -4
  3. {equimo-0.2.3 → equimo-0.3.0}/Equimo.egg-info/requires.txt +9 -0
  4. {equimo-0.2.3 → equimo-0.3.0}/PKG-INFO +86 -3
  5. {equimo-0.2.3 → equimo-0.3.0}/README.md +78 -2
  6. equimo-0.3.0/equimo/__init__.py +1 -0
  7. {equimo-0.2.3 → equimo-0.3.0}/equimo/io.py +55 -1
  8. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/attention.py +12 -1
  9. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/ffn.py +8 -0
  10. {equimo-0.2.3 → equimo-0.3.0}/equimo/utils.py +78 -0
  11. {equimo-0.2.3 → equimo-0.3.0}/pyproject.toml +14 -1
  12. {equimo-0.2.3 → equimo-0.3.0}/tests/test_models.py +5 -3
  13. equimo-0.2.3/equimo/__init__.py +0 -1
  14. equimo-0.2.3/equimo/converters/__init__.py +0 -1
  15. equimo-0.2.3/equimo/converters/dinov2.py +0 -69
  16. equimo-0.2.3/equimo/converters/siglip2.py +0 -105
  17. equimo-0.2.3/equimo/converters/utils.py +0 -205
  18. {equimo-0.2.3 → equimo-0.3.0}/Equimo.egg-info/dependency_links.txt +0 -0
  19. {equimo-0.2.3 → equimo-0.3.0}/Equimo.egg-info/top_level.txt +0 -0
  20. {equimo-0.2.3 → equimo-0.3.0}/LICENSE.md +0 -0
  21. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/__init__.py +0 -0
  22. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/activation.py +0 -0
  23. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/convolution.py +0 -0
  24. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/downsample.py +0 -0
  25. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/dropout.py +0 -0
  26. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/generic.py +0 -0
  27. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/mamba.py +0 -0
  28. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/norm.py +0 -0
  29. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/patch.py +0 -0
  30. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/posemb.py +0 -0
  31. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/sharing.py +0 -0
  32. {equimo-0.2.3 → equimo-0.3.0}/equimo/layers/squeeze_excite.py +0 -0
  33. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/__init__.py +0 -0
  34. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/emamodel.py +0 -0
  35. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/fastervit.py +0 -0
  36. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/mlla.py +0 -0
  37. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/partialformer.py +0 -0
  38. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/shvit.py +0 -0
  39. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/vit.py +0 -0
  40. {equimo-0.2.3 → equimo-0.3.0}/equimo/models/vssd.py +0 -0
  41. {equimo-0.2.3 → equimo-0.3.0}/equimo/ops/scan.py +0 -0
  42. {equimo-0.2.3 → equimo-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Equimo
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -24,6 +24,13 @@ Requires-Dist: semver>=3.0.4
24
24
  Provides-Extra: torch
25
25
  Requires-Dist: torch>=2.6.0; extra == "torch"
26
26
  Requires-Dist: timm>=1.0.15; extra == "torch"
27
+ Provides-Extra: text
28
+ Requires-Dist: tensorflow>=2.18.1; extra == "text"
29
+ Requires-Dist: tensorflow-text>=2.18.1; extra == "text"
30
+ Provides-Extra: extras
31
+ Requires-Dist: matplotlib>=3.10.1; extra == "extras"
32
+ Requires-Dist: pillow>=11.1.0; extra == "extras"
33
+ Requires-Dist: scikit-learn>=1.6.1; extra == "extras"
27
34
 
28
35
  # Equimo: Modern Vision Models in JAX/Equinox
29
36
 
@@ -37,6 +44,7 @@ Equimo (Equinox Image Models) provides JAX/Equinox implementations of recent com
37
44
  - Focus on recent architectures (2023-2024 papers)
38
45
  - Modular design for easy experimentation
39
46
  - Extensive documentation and type hints
47
+ - **Experimental** support for text embedding
40
48
 
41
49
  ## Installation
42
50
 
@@ -98,6 +106,76 @@ x = jax.random.normal(key, (3, 224, 224))
98
106
  output = model(x, enable_dropout=False, key=key)
99
107
  ```
100
108
 
109
+ ## Working with text embeddings
110
+
111
+ **Warning: this is experimental, it can break or change at any time**
112
+
113
+ `equimo.experimental.text` has been added since v0.3.0. It allows working with both text and images. It is especially
114
+ useful for models like SigLIP or TIPS, although only TIPS is currently supported.
115
+
116
+ Currently, text tokenization relies on tensorflow_text, install equimo with the `text` group such as
117
+ `uv add equimo[text]`.
118
+
119
+ Here is a very simple example of a 0-shot classification based on the comparison between text and image embeddings:
120
+
121
+ ```python
122
+ import jax
123
+ from einops import rearrange
124
+
125
+ from equimo.experimental.text import Tokenizer
126
+ from equimo.io import load_image, load_model
127
+ from equimo.utils import PCAVisualizer, normalize, plot_image_and_feature_map
128
+
129
+ # Random demo inputs
130
+ key = jax.random.PRNGKey(42)
131
+ image = load_image("./demo.jpg", size=448)
132
+ text = [
133
+ "A baby discovering happiness",
134
+ "A computer",
135
+ ]
136
+
137
+ # Loading pretrained models
138
+ image_encoder = load_model("vit", "tips_vits14_hr")
139
+ text_encoder = load_model("experimental.textencoder", "tips_vits14_hr_text")
140
+
141
+ # Encoding text and image
142
+ ids, paddings = Tokenizer(identifier="sentencepiece_tips").tokenize(text, max_len=64)
143
+
144
+ text_embedding = normalize(
145
+ jax.vmap(text_encoder, in_axes=(0, 0, None))(ids, paddings, key)
146
+ )
147
+ image_embedding = jax.vmap(image_encoder.norm)(image_encoder.features(image, key))
148
+ cls_token = normalize(image_embedding[0])
149
+ spatial_features = rearrange(
150
+ image_embedding[2:], "(h w) d -> h w d", h=int(448 / 14), w=int(448 / 14)
151
+ )
152
+
153
+ # Getting probabilities based on Cosine Similarity
154
+ cos_sim = jax.nn.softmax(
155
+ ((cls_token[None, :] @ text_embedding.T) / text_encoder.temperature), axis=-1
156
+ )
157
+
158
+ # Plot the results
159
+ label_idxs = jax.numpy.argmax(cos_sim, axis=-1)
160
+ cos_sim_max = jax.numpy.max(cos_sim, axis=-1)
161
+ label_predicted = text[label_idxs[0]]
162
+ similarity = cos_sim_max[0]
163
+ pca_obj = PCAVisualizer(spatial_features)
164
+ image_pca = pca_obj(spatial_features)
165
+
166
+ plot_image_and_feature_map(
167
+ image.transpose(1, 2, 0),
168
+ image_pca,
169
+ "./out.png",
170
+ "Input Image",
171
+ f"{label_predicted}, prob: {similarity * 100:.2f}%",
172
+ )
173
+ ```
174
+
175
+ Resulting in such a wonderful result:
176
+
177
+ ![Output of TIPS 0-shot classification](./docs/assets/0-shot.png)
178
+
101
179
  ## Saving and Loading Models
102
180
 
103
181
  Equimo provides utilities for saving models locally and loading pre-trained models from the
@@ -155,9 +233,13 @@ model = load_model(
155
233
 
156
234
  #### List of pretrained models
157
235
 
158
- Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) and SigLIP2 have been ported.
236
+ The following models have pretrained weights available in Equimo:
237
+
238
+ - [DinoV2](https://arxiv.org/abs/2304.07193),
239
+ - [SigLIP2](https://arxiv.org/abs/2502.14786),
240
+ - [TIPS](https://arxiv.org/abs/2410.16512).
159
241
 
160
- Model identifiers allows downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
242
+ Model identifiers allow downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
161
243
 
162
244
  Identifiers are filenames without the extensions, such as:
163
245
 
@@ -165,6 +247,7 @@ Identifiers are filenames without the extensions, such as:
165
247
  - `dinov2_vits14_reg`
166
248
  - `siglip2_vitl16_512`
167
249
  - `siglip2_vitso400m16_384`
250
+ - `tips_vitg14_lr`
168
251
 
169
252
  ## Contributing
170
253
 
@@ -9,10 +9,6 @@ Equimo.egg-info/top_level.txt
9
9
  equimo/__init__.py
10
10
  equimo/io.py
11
11
  equimo/utils.py
12
- equimo/converters/__init__.py
13
- equimo/converters/dinov2.py
14
- equimo/converters/siglip2.py
15
- equimo/converters/utils.py
16
12
  equimo/layers/__init__.py
17
13
  equimo/layers/activation.py
18
14
  equimo/layers/attention.py
@@ -7,6 +7,15 @@ lz4>=4.4.3
7
7
  requests>=2.32.3
8
8
  semver>=3.0.4
9
9
 
10
+ [extras]
11
+ matplotlib>=3.10.1
12
+ pillow>=11.1.0
13
+ scikit-learn>=1.6.1
14
+
15
+ [text]
16
+ tensorflow>=2.18.1
17
+ tensorflow-text>=2.18.1
18
+
10
19
  [torch]
11
20
  torch>=2.6.0
12
21
  timm>=1.0.15
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Equimo
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -24,6 +24,13 @@ Requires-Dist: semver>=3.0.4
24
24
  Provides-Extra: torch
25
25
  Requires-Dist: torch>=2.6.0; extra == "torch"
26
26
  Requires-Dist: timm>=1.0.15; extra == "torch"
27
+ Provides-Extra: text
28
+ Requires-Dist: tensorflow>=2.18.1; extra == "text"
29
+ Requires-Dist: tensorflow-text>=2.18.1; extra == "text"
30
+ Provides-Extra: extras
31
+ Requires-Dist: matplotlib>=3.10.1; extra == "extras"
32
+ Requires-Dist: pillow>=11.1.0; extra == "extras"
33
+ Requires-Dist: scikit-learn>=1.6.1; extra == "extras"
27
34
 
28
35
  # Equimo: Modern Vision Models in JAX/Equinox
29
36
 
@@ -37,6 +44,7 @@ Equimo (Equinox Image Models) provides JAX/Equinox implementations of recent com
37
44
  - Focus on recent architectures (2023-2024 papers)
38
45
  - Modular design for easy experimentation
39
46
  - Extensive documentation and type hints
47
+ - **Experimental** support for text embedding
40
48
 
41
49
  ## Installation
42
50
 
@@ -98,6 +106,76 @@ x = jax.random.normal(key, (3, 224, 224))
98
106
  output = model(x, enable_dropout=False, key=key)
99
107
  ```
100
108
 
109
+ ## Working with text embeddings
110
+
111
+ **Warning: this is experimental, it can break or change at any time**
112
+
113
+ `equimo.experimental.text` has been added since v0.3.0. It allows working with both text and images. It is especially
114
+ useful for models like SigLIP or TIPS, although only TIPS is currently supported.
115
+
116
+ Currently, text tokenization relies on tensorflow_text, install equimo with the `text` group such as
117
+ `uv add equimo[text]`.
118
+
119
+ Here is a very simple example of a 0-shot classification based on the comparison between text and image embeddings:
120
+
121
+ ```python
122
+ import jax
123
+ from einops import rearrange
124
+
125
+ from equimo.experimental.text import Tokenizer
126
+ from equimo.io import load_image, load_model
127
+ from equimo.utils import PCAVisualizer, normalize, plot_image_and_feature_map
128
+
129
+ # Random demo inputs
130
+ key = jax.random.PRNGKey(42)
131
+ image = load_image("./demo.jpg", size=448)
132
+ text = [
133
+ "A baby discovering happiness",
134
+ "A computer",
135
+ ]
136
+
137
+ # Loading pretrained models
138
+ image_encoder = load_model("vit", "tips_vits14_hr")
139
+ text_encoder = load_model("experimental.textencoder", "tips_vits14_hr_text")
140
+
141
+ # Encoding text and image
142
+ ids, paddings = Tokenizer(identifier="sentencepiece_tips").tokenize(text, max_len=64)
143
+
144
+ text_embedding = normalize(
145
+ jax.vmap(text_encoder, in_axes=(0, 0, None))(ids, paddings, key)
146
+ )
147
+ image_embedding = jax.vmap(image_encoder.norm)(image_encoder.features(image, key))
148
+ cls_token = normalize(image_embedding[0])
149
+ spatial_features = rearrange(
150
+ image_embedding[2:], "(h w) d -> h w d", h=int(448 / 14), w=int(448 / 14)
151
+ )
152
+
153
+ # Getting probabilities based on Cosine Similarity
154
+ cos_sim = jax.nn.softmax(
155
+ ((cls_token[None, :] @ text_embedding.T) / text_encoder.temperature), axis=-1
156
+ )
157
+
158
+ # Plot the results
159
+ label_idxs = jax.numpy.argmax(cos_sim, axis=-1)
160
+ cos_sim_max = jax.numpy.max(cos_sim, axis=-1)
161
+ label_predicted = text[label_idxs[0]]
162
+ similarity = cos_sim_max[0]
163
+ pca_obj = PCAVisualizer(spatial_features)
164
+ image_pca = pca_obj(spatial_features)
165
+
166
+ plot_image_and_feature_map(
167
+ image.transpose(1, 2, 0),
168
+ image_pca,
169
+ "./out.png",
170
+ "Input Image",
171
+ f"{label_predicted}, prob: {similarity * 100:.2f}%",
172
+ )
173
+ ```
174
+
175
+ Resulting in such a wonderful result:
176
+
177
+ ![Output of TIPS 0-shot classification](./docs/assets/0-shot.png)
178
+
101
179
  ## Saving and Loading Models
102
180
 
103
181
  Equimo provides utilities for saving models locally and loading pre-trained models from the
@@ -155,9 +233,13 @@ model = load_model(
155
233
 
156
234
  #### List of pretrained models
157
235
 
158
- Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) and SigLIP2 have been ported.
236
+ The following models have pretrained weights available in Equimo:
237
+
238
+ - [DinoV2](https://arxiv.org/abs/2304.07193),
239
+ - [SigLIP2](https://arxiv.org/abs/2502.14786),
240
+ - [TIPS](https://arxiv.org/abs/2410.16512).
159
241
 
160
- Model identifiers allows downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
242
+ Model identifiers allow downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
161
243
 
162
244
  Identifiers are filenames without the extensions, such as:
163
245
 
@@ -165,6 +247,7 @@ Identifiers are filenames without the extensions, such as:
165
247
  - `dinov2_vits14_reg`
166
248
  - `siglip2_vitl16_512`
167
249
  - `siglip2_vitso400m16_384`
250
+ - `tips_vitg14_lr`
168
251
 
169
252
  ## Contributing
170
253
 
@@ -10,6 +10,7 @@ Equimo (Equinox Image Models) provides JAX/Equinox implementations of recent com
10
10
  - Focus on recent architectures (2023-2024 papers)
11
11
  - Modular design for easy experimentation
12
12
  - Extensive documentation and type hints
13
+ - **Experimental** support for text embedding
13
14
 
14
15
  ## Installation
15
16
 
@@ -71,6 +72,76 @@ x = jax.random.normal(key, (3, 224, 224))
71
72
  output = model(x, enable_dropout=False, key=key)
72
73
  ```
73
74
 
75
+ ## Working with text embeddings
76
+
77
+ **Warning: this is experimental, it can break or change at any time**
78
+
79
+ `equimo.experimental.text` has been added since v0.3.0. It allows working with both text and images. It is especially
80
+ useful for models like SigLIP or TIPS, although only TIPS is currently supported.
81
+
82
+ Currently, text tokenization relies on tensorflow_text, install equimo with the `text` group such as
83
+ `uv add equimo[text]`.
84
+
85
+ Here is a very simple example of a 0-shot classification based on the comparison between text and image embeddings:
86
+
87
+ ```python
88
+ import jax
89
+ from einops import rearrange
90
+
91
+ from equimo.experimental.text import Tokenizer
92
+ from equimo.io import load_image, load_model
93
+ from equimo.utils import PCAVisualizer, normalize, plot_image_and_feature_map
94
+
95
+ # Random demo inputs
96
+ key = jax.random.PRNGKey(42)
97
+ image = load_image("./demo.jpg", size=448)
98
+ text = [
99
+ "A baby discovering happiness",
100
+ "A computer",
101
+ ]
102
+
103
+ # Loading pretrained models
104
+ image_encoder = load_model("vit", "tips_vits14_hr")
105
+ text_encoder = load_model("experimental.textencoder", "tips_vits14_hr_text")
106
+
107
+ # Encoding text and image
108
+ ids, paddings = Tokenizer(identifier="sentencepiece_tips").tokenize(text, max_len=64)
109
+
110
+ text_embedding = normalize(
111
+ jax.vmap(text_encoder, in_axes=(0, 0, None))(ids, paddings, key)
112
+ )
113
+ image_embedding = jax.vmap(image_encoder.norm)(image_encoder.features(image, key))
114
+ cls_token = normalize(image_embedding[0])
115
+ spatial_features = rearrange(
116
+ image_embedding[2:], "(h w) d -> h w d", h=int(448 / 14), w=int(448 / 14)
117
+ )
118
+
119
+ # Getting probabilities based on Cosine Similarity
120
+ cos_sim = jax.nn.softmax(
121
+ ((cls_token[None, :] @ text_embedding.T) / text_encoder.temperature), axis=-1
122
+ )
123
+
124
+ # Plot the results
125
+ label_idxs = jax.numpy.argmax(cos_sim, axis=-1)
126
+ cos_sim_max = jax.numpy.max(cos_sim, axis=-1)
127
+ label_predicted = text[label_idxs[0]]
128
+ similarity = cos_sim_max[0]
129
+ pca_obj = PCAVisualizer(spatial_features)
130
+ image_pca = pca_obj(spatial_features)
131
+
132
+ plot_image_and_feature_map(
133
+ image.transpose(1, 2, 0),
134
+ image_pca,
135
+ "./out.png",
136
+ "Input Image",
137
+ f"{label_predicted}, prob: {similarity * 100:.2f}%",
138
+ )
139
+ ```
140
+
141
+ Resulting in such a wonderful result:
142
+
143
+ ![Output of TIPS 0-shot classification](./docs/assets/0-shot.png)
144
+
74
145
  ## Saving and Loading Models
75
146
 
76
147
  Equimo provides utilities for saving models locally and loading pre-trained models from the
@@ -128,9 +199,13 @@ model = load_model(
128
199
 
129
200
  #### List of pretrained models
130
201
 
131
- Currently, only [DinoV2](https://github.com/facebookresearch/dinov2/) and SigLIP2 have been ported.
202
+ The following models have pretrained weights available in Equimo:
203
+
204
+ - [DinoV2](https://arxiv.org/abs/2304.07193),
205
+ - [SigLIP2](https://arxiv.org/abs/2502.14786),
206
+ - [TIPS](https://arxiv.org/abs/2410.16512).
132
207
 
133
- Model identifiers allows downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
208
+ Model identifiers allow downloading from equimo's [repository on huggingface](https://huggingface.co/poiretclement/equimo/tree/main/models/default)
134
209
 
135
210
  Identifiers are filenames without the extensions, such as:
136
211
 
@@ -138,6 +213,7 @@ Identifiers are filenames without the extensions, such as:
138
213
  - `dinov2_vits14_reg`
139
214
  - `siglip2_vitl16_512`
140
215
  - `siglip2_vitso400m16_384`
216
+ - `tips_vitg14_lr`
141
217
 
142
218
  ## Contributing
143
219
 
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -1,10 +1,13 @@
1
1
  import json
2
+ import io
2
3
  import tarfile
3
4
  import tempfile
4
5
  from pathlib import Path
6
+ from typing import Optional
5
7
 
6
8
  import equinox as eqx
7
9
  import jax
10
+ import jax.numpy as jnp
8
11
  import lz4.frame
9
12
  import requests
10
13
  from loguru import logger
@@ -72,6 +75,7 @@ def save_model(
72
75
  eqx.tree_serialise_leaves(tmp_path / "weights.eqx", model)
73
76
 
74
77
  # Create compressed archive
78
+ path.parent.mkdir(parents=True, exist_ok=True)
75
79
  with lz4.frame.open(path, "wb") as f_out:
76
80
  with tarfile.open(fileobj=f_out, mode="w") as tar:
77
81
  tar.add(tmp_path / "metadata.json", arcname="metadata.json")
@@ -130,6 +134,7 @@ def load_model(
130
134
  identifier: str | None = None,
131
135
  path: Path | None = None,
132
136
  repository: str = DEFAULT_REPOSITORY_URL,
137
+ inference_mode: bool = True,
133
138
  **model_kwargs,
134
139
  ) -> eqx.Module:
135
140
  """Load an Equinox model from either a local path or remote repository.
@@ -143,6 +148,8 @@ def load_model(
143
148
  Mutually exclusive with identifier. Defaults to None.
144
149
  repository (str, optional): Base URL for model download.
145
150
  Defaults to DEFAULT_REPOSITORY_URL.
151
+ inference_mode (bool): Disables dropouts if True.
152
+ Defaults to True.
146
153
  model_kwargs: kwargs passed to model instanciation. Overrides metadatas.
147
154
 
148
155
  Returns:
@@ -218,6 +225,10 @@ def load_model(
218
225
  model_cls = em.FasterViT
219
226
  case "partialformer":
220
227
  model_cls = em.PartialFormer
228
+ case "experimental.textencoder":
229
+ from equimo.experimental.text import TextEncoder
230
+
231
+ model_cls = TextEncoder
221
232
  case _:
222
233
  raise ValueError(f"Unknown model class: {cls}")
223
234
 
@@ -225,9 +236,52 @@ def load_model(
225
236
  kwargs = metadata["model_config"] | model_kwargs
226
237
  model = model_cls(**kwargs, key=jax.random.PRNGKey(42))
227
238
 
228
- # Load weights
239
+ # Load weights and set inference mode
229
240
  model = eqx.tree_deserialise_leaves(load_path / "weights.eqx", model)
241
+ model = eqx.nn.inference_mode(model, inference_mode)
230
242
 
231
243
  logger.info("Model loaded successfully.")
232
244
 
233
245
  return model
246
+
247
+
248
+ def load_image(
249
+ path: str,
250
+ mean: Optional[list[float]] = None,
251
+ std: Optional[list[float]] = None,
252
+ size: Optional[int] = None,
253
+ ):
254
+ """Load an image and perform minor preprocessing.
255
+
256
+ Args:
257
+ path (str): Path of the image.
258
+ mean (list, optional): Channel mean for normalization.
259
+ std (list, optional): Channel std for normalization.
260
+ size (int, optional): Size to which resize the image.
261
+
262
+ Returns:
263
+ jnp.array: The loaded image.
264
+
265
+ Raises:
266
+ ImportError: If PIL can't be loaded.
267
+ """
268
+ try:
269
+ from PIL import Image
270
+ except ImportError:
271
+ raise ImportError("PIL is needed to be able to load images.")
272
+
273
+ with open(path, "rb") as fd:
274
+ image_bytes = io.BytesIO(fd.read())
275
+ pil_image = Image.open(image_bytes)
276
+
277
+ array = jnp.array(pil_image).astype(jnp.float32) / 255.0
278
+ if size is not None:
279
+ array = jax.image.resize(array, (size, size, 3), method="bilinear")
280
+
281
+ if mean is not None and std is not None:
282
+ mean = jnp.array(mean)[None, None, :]
283
+ std = jnp.array(std)[None, None, :]
284
+
285
+ array = (array - mean) / std
286
+
287
+ return array.transpose(2, 0, 1)
@@ -1,5 +1,4 @@
1
1
  from typing import Callable, List, Optional, Tuple
2
-
3
2
  import equinox as eqx
4
3
  import jax
5
4
  import jax.numpy as jnp
@@ -74,6 +73,7 @@ class Attention(eqx.Module):
74
73
  x: Float[Array, "seqlen dim"],
75
74
  key: PRNGKeyArray,
76
75
  inference: Optional[bool] = None,
76
+ mask: Optional[Float[Array, ""]] = None,
77
77
  ) -> Float[Array, "seqlen dim"]:
78
78
  key1, key2 = jr.split(key, 2)
79
79
 
@@ -90,6 +90,10 @@ class Attention(eqx.Module):
90
90
  k = jax.vmap(jax.vmap(self.k_norm))(k)
91
91
 
92
92
  attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
93
+
94
+ if mask is not None:
95
+ attn = jnp.where(mask == 0, jnp.finfo(attn.dtype).min, attn)
96
+
93
97
  attn = jax.nn.softmax(attn, axis=-1)
94
98
  attn = self.attn_drop(attn, inference=inference, key=key1)
95
99
 
@@ -312,9 +316,14 @@ class AttentionBlock(eqx.Module):
312
316
  x: Float[Array, "seqlen dim"],
313
317
  key: PRNGKeyArray,
314
318
  inference: Optional[bool] = None,
319
+ mask: Optional[Float[Array, ""]] = None,
315
320
  ) -> Float[Array, "seqlen dim"]:
316
321
  key_attn, key_mlp, key_dr1, key_dr2 = jr.split(key, 4)
317
322
 
323
+ # I chose to define extra args here rather than passing mask directly
324
+ # because not all attention mechanisms support masks as args
325
+ extra_kwargs = {"mask": mask} if mask is not None else {}
326
+
318
327
  x = self.drop_path1(
319
328
  x,
320
329
  self.ls1(
@@ -323,6 +332,7 @@ class AttentionBlock(eqx.Module):
323
332
  jax.vmap(self.prenorm)(x),
324
333
  inference=inference,
325
334
  key=key_attn,
335
+ **extra_kwargs,
326
336
  )
327
337
  )
328
338
  ),
@@ -336,6 +346,7 @@ class AttentionBlock(eqx.Module):
336
346
  jax.vmap(self.norm)(x),
337
347
  inference=inference,
338
348
  key=key_mlp,
349
+ **extra_kwargs,
339
350
  )
340
351
  ),
341
352
  inference=inference,
@@ -210,6 +210,7 @@ class Mlp(eqx.Module):
210
210
  self,
211
211
  x: Float[Array, "seqlen dim"],
212
212
  key: PRNGKeyArray,
213
+ mask: Optional[Float[Array, ""]] = None,
213
214
  inference: Optional[bool] = None,
214
215
  ) -> Float[Array, "seqlen dim"]:
215
216
  key_dr1, key_dr2 = jr.split(key, 2)
@@ -219,12 +220,19 @@ class Mlp(eqx.Module):
219
220
  inference=inference,
220
221
  key=key_dr1,
221
222
  )
223
+
224
+ if mask is not None:
225
+ x *= mask
226
+
222
227
  x = self.drop2(
223
228
  jax.vmap(self.fc2)(x),
224
229
  inference=inference,
225
230
  key=key_dr2,
226
231
  )
227
232
 
233
+ if mask is not None:
234
+ x *= mask
235
+
228
236
  return x
229
237
 
230
238
 
@@ -4,6 +4,84 @@ import jax
4
4
  import jax.numpy as jnp
5
5
  from jaxtyping import Array, Float
6
6
 
7
+ import typing as t
8
+ import numpy as np
9
+
10
+
11
+ _ArrayLike = t.Union[np.ndarray, jnp.ndarray]
12
+
13
+
14
+ def normalize(x, order: int = 2):
15
+ return x / np.linalg.norm(x, ord=order, axis=-1, keepdims=True).clip(min=1e-3)
16
+
17
+
18
+ class PCAVisualizer:
19
+ """PCA visualizer.
20
+
21
+ Taken as-is from https://github.com/google-deepmind/tips/blob/72820c1841f973c9543d9c95c5ff2262ec621955/scenic/utils/feature_viz.py#L32
22
+ """
23
+
24
+ def __init__(
25
+ self, features: _ArrayLike, n_samples: int = 100000, n_components: int = 3
26
+ ) -> None:
27
+ """Creates a PCA object for visualizing features of shape [..., F]."""
28
+ try:
29
+ from sklearn import decomposition
30
+ except ImportError:
31
+ raise ImportError(
32
+ "You need sklearn to use the PCAVisualizer, install it using `uv add equimo[viz]`"
33
+ )
34
+
35
+ features = np.array(features)
36
+ pca_object = decomposition.PCA(n_components=n_components)
37
+ features = features.reshape([-1, features.shape[-1]])
38
+ features = features[np.random.randint(0, features.shape[0], n_samples), :]
39
+ pca_object.fit(features)
40
+ self.pca_object = pca_object
41
+ self.n_components = n_components
42
+
43
+ def __call__(self, features: _ArrayLike) -> np.ndarray:
44
+ """Apply PCA to features of shape [..., F]."""
45
+ features = np.array(features)
46
+ features_pca = self.pca_object.transform(
47
+ features.reshape([-1, features.shape[-1]])
48
+ ).reshape(features.shape[:-1] + (self.n_components,))
49
+ return normalize(features_pca) * 0.5 + 0.5
50
+
51
+
52
+ def plot_image_and_feature_map(
53
+ image,
54
+ feature_map,
55
+ save_path,
56
+ image_title="Image",
57
+ map_title="Feature Map",
58
+ fontsize=16,
59
+ ):
60
+ """
61
+ Plots an image and its feature map side by side and saves the figure.
62
+ """
63
+ try:
64
+ import matplotlib.pyplot as plt
65
+ except ImportError:
66
+ raise ImportError("Matplotlib is required to plot an image")
67
+
68
+ feature_map_normalized = (feature_map - feature_map.min()) / (
69
+ feature_map.max() - feature_map.min()
70
+ )
71
+
72
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
73
+ ax1.imshow(image)
74
+ ax1.set_title(image_title, fontsize=fontsize)
75
+ ax1.axis("off")
76
+
77
+ ax2.imshow(feature_map_normalized, cmap="gray")
78
+ ax2.set_title(map_title, fontsize=fontsize)
79
+ ax2.axis("off")
80
+
81
+ plt.tight_layout()
82
+ plt.savefig(save_path)
83
+ plt.close()
84
+
7
85
 
8
86
  def make_2tuple(x):
9
87
  """Convert input into a 2-tuple.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Equimo"
3
- version = "0.2.3"
3
+ version = "0.3.0"
4
4
  description = "Implementation of popular vision models in Jax"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -31,10 +31,22 @@ classifiers = [
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
34
+ # Useful to convert models from PyTorch
34
35
  torch = [
35
36
  "torch>=2.6.0",
36
37
  "timm>=1.0.15",
37
38
  ]
39
+ # Required for equimo.experimental.text (text tokenizer)
40
+ text = [
41
+ "tensorflow>=2.18.1",
42
+ "tensorflow-text>=2.18.1",
43
+ ]
44
+ # Required for some external utils
45
+ extras = [
46
+ "matplotlib>=3.10.1",
47
+ "pillow>=11.1.0",
48
+ "scikit-learn>=1.6.1",
49
+ ]
38
50
 
39
51
  [dependency-groups]
40
52
  dev = [
@@ -43,3 +55,4 @@ dev = [
43
55
 
44
56
  [tool.setuptools]
45
57
  license-files = []
58
+ packages = ["equimo"]
@@ -75,7 +75,9 @@ def test_save_load_model_compressed():
75
75
  save_model(save_path, model, model_config, torch_hub_cfg, compression=True)
76
76
 
77
77
  # Load model
78
- loaded_model = load_model(cls="vit", path=save_path.with_suffix(".tar.lz4"))
78
+ loaded_model = load_model(
79
+ cls="vit", path=save_path.with_suffix(".tar.lz4"), dynamic_img_size=True
80
+ )
79
81
 
80
82
  # Test loaded model
81
83
  loaded_output = loaded_model.features(x, key=key)
@@ -116,7 +118,7 @@ def test_save_load_model_uncompressed():
116
118
 
117
119
  save_model(save_path, model, model_config, torch_hub_cfg, compression=False)
118
120
 
119
- loaded_model = load_model(cls="vit", path=save_path)
121
+ loaded_model = load_model(cls="vit", path=save_path, dynamic_img_size=True)
120
122
  loaded_output = loaded_model.features(x, key=key)
121
123
 
122
124
  assert jnp.allclose(original_output, loaded_output, atol=1e-5)
@@ -125,7 +127,7 @@ def test_save_load_model_uncompressed():
125
127
  def test_load_pretrained_model():
126
128
  """Test loading a pretrained model from the repository."""
127
129
  key = jr.PRNGKey(42)
128
- model = load_model(cls="vit", identifier="dinov2_vits14_reg")
130
+ model = load_model(cls="vit", identifier="dinov2_vits14_reg", dynamic_img_size=True)
129
131
 
130
132
  # Test inference
131
133
  x = jr.normal(key, (3, 224, 224))
@@ -1 +0,0 @@
1
- __version__ = "0.2.3"
@@ -1 +0,0 @@
1
-
@@ -1,69 +0,0 @@
1
- from pathlib import Path
2
-
3
- import jax
4
-
5
- import equimo.models as em
6
- from equimo.converters.utils import convert_torch_to_equinox
7
- from equimo.io import load_model, save_model
8
-
9
-
10
- def main():
11
- key = jax.random.PRNGKey(42)
12
- dinov2_vits14_reg_config = {
13
- "img_size": 518,
14
- "in_channels": 3,
15
- "dim": 384,
16
- "patch_size": 14,
17
- "num_heads": [6],
18
- "depths": [12],
19
- "num_classes": 0,
20
- "use_mask_token": True,
21
- "reg_tokens": 4,
22
- "init_values": 1e-5,
23
- "eps": 1e-6,
24
- "dynamic_img_size": True,
25
- }
26
-
27
- dinov2_vits14_reg = em.VisionTransformer(
28
- **dinov2_vits14_reg_config,
29
- key=key,
30
- )
31
-
32
- torch_hub_cfg = ["facebookresearch/dinov2", "dinov2_vits14_reg"]
33
-
34
- replace_cfg = {
35
- "reg_tokens": "register_tokens",
36
- "blocks.0.blocks": "blocks",
37
- ".prenorm.": ".norm1.",
38
- ".norm.": ".norm2.",
39
- }
40
- expand_cfg = {"patch_embed.proj.bias": ["after", 2]}
41
- squeeze_cfg = {
42
- "pos_embed": 0,
43
- "cls_token": 0,
44
- "register_tokens": 0,
45
- }
46
- whitelist = []
47
-
48
- dinov2_vits14_reg = convert_torch_to_equinox(
49
- dinov2_vits14_reg,
50
- torch_hub_cfg,
51
- replace_cfg,
52
- expand_cfg,
53
- squeeze_cfg,
54
- whitelist,
55
- strict=True,
56
- )
57
-
58
- save_model(
59
- Path("~/.cache/equimo/dinov2_vits14_reg").expanduser(),
60
- dinov2_vits14_reg,
61
- dinov2_vits14_reg_config,
62
- torch_hub_cfg,
63
- compression=True,
64
- )
65
-
66
- _ = load_model(
67
- cls="vit",
68
- path=Path("~/.cache/equimo/dinov2_vits14_reg.tar.lz4").expanduser(),
69
- )
@@ -1,105 +0,0 @@
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,205 +0,0 @@
1
- import re
2
- from typing import Any, Dict, Literal, Optional, Tuple
3
-
4
- import equinox as eqx
5
- import jax
6
- import jax.numpy as jnp
7
- from jax.tree_util import GetAttrKey, SequenceKey
8
- from loguru import logger
9
-
10
-
11
- def stringify_name(path: Tuple) -> str:
12
- stringified = []
13
- for p in path:
14
- if isinstance(p, GetAttrKey):
15
- stringified.append(p.name)
16
- if isinstance(p, SequenceKey):
17
- stringified.append(str(p.idx))
18
- return ".".join(stringified)
19
-
20
-
21
- def expand_torch_tensor(tensor, pos: str, n: int):
22
- padding = [None] * n
23
- match pos:
24
- case "before":
25
- return tensor[*padding, ...]
26
- case "after":
27
- return tensor[..., *padding]
28
- case _:
29
- raise ValueError(
30
- f"Invalid `pos`, expected one of [`before`, `after`], got: {pos}"
31
- )
32
-
33
-
34
- def convert_params_from_torch_hub(
35
- jax_model: eqx.Module,
36
- replace_cfg: Dict[str, str],
37
- expand_cfg: Dict[str, list],
38
- squeeze_cfg: Dict[str, int | None],
39
- whitelist: list[str],
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,
45
- ):
46
- """
47
- Load weights from a torch hub model into an Equinox module.
48
-
49
- Args:
50
- jax_model (eqx.Module): A preexisting Jax model corresponding to the checkpoint to download.
51
- torch_hub_cfg (Tuple[str]): Arguments passed to `torch.hub.load()`.
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.
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.
61
- """
62
- try:
63
- import timm
64
- import torch
65
- except:
66
- raise ImportError("`torch` not available")
67
-
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
-
83
- torch_params = dict(torch_model.named_parameters())
84
-
85
- # Extract the parameters from the defined Jax model
86
- jax_params = eqx.filter(jax_model, eqx.is_array)
87
- # _, jax_params, _ = nnx.split(jax_model, nnx.Param, ...)
88
- jax_params_flat, jax_param_pytree = jax.tree_util.tree_flatten_with_path(jax_params)
89
-
90
- torch_params_flat = []
91
- for path, param in jax_params_flat:
92
- # Match the parameters' path of pytorch
93
- param_path = stringify_name(path)
94
- param_path = re.sub(r"\.scale|.kernel", ".weight", param_path)
95
-
96
- for old, new in replace_cfg.items():
97
- param_path = param_path.replace(old, new)
98
-
99
- shape = param.shape
100
-
101
- if param_path not in torch_params:
102
- _msg = f"{param_path} ({shape}) not found in PyTorch model."
103
- if strict:
104
- logger.error(_msg)
105
- raise AttributeError(_msg)
106
-
107
- logger.warning(f"{_msg} Appending `None` to flat param list.")
108
- torch_params_flat.append(None)
109
- continue
110
-
111
- logger.info(f"Converting {param_path}...")
112
- torch_param = torch_params[param_path]
113
-
114
- if param_path in expand_cfg:
115
- torch_param = expand_torch_tensor(torch_param, *expand_cfg[param_path])
116
- if param_path in squeeze_cfg:
117
- torch_param = torch.squeeze(torch_param, dim=squeeze_cfg[param_path])
118
-
119
- if shape != torch_param.shape:
120
- _msg = f"`{param_path}`: expected shape ({shape}) does not match its pytorch implementation ({torch_param.shape})."
121
- logger.error(_msg)
122
- raise ValueError(_msg)
123
-
124
- torch_params_flat.append(jnp.asarray(torch_param.detach().numpy()))
125
- _ = torch_params.pop(param_path)
126
-
127
- loaded_params = jax.tree_util.tree_unflatten(jax_param_pytree, torch_params_flat)
128
-
129
- for path, param in torch_params.items():
130
- logger.warning(
131
- f"PyTorch parameters `{path}` ({param.shape}) were not converted."
132
- )
133
- if strict and path not in whitelist:
134
- _msg = f"The PyTorch model contains parameters ({path}) that do not have a Jax counterpart."
135
- logger.error(_msg)
136
- raise AttributeError(_msg)
137
-
138
- if return_torch:
139
- return loaded_params, torch_model
140
- return loaded_params
141
-
142
-
143
- def convert_torch_to_equinox(
144
- jax_model: eqx.Module,
145
- replace_cfg: dict = {},
146
- expand_cfg: dict = {},
147
- squeeze_cfg: dict = {},
148
- whitelist: list[str] = [],
149
- strict: bool = True,
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]:
155
- """
156
- Convert a PyTorch model from torch.hub to Equinox format.
157
-
158
- Args:
159
- jax_model: The Equinox model
160
- replace_cfg: Dict of parameter name replacements
161
- expand_cfg: Dict of dimensions to expand
162
- squeeze_cfg: Dict of dimensions to squeeze
163
- whitelist: List of parameters to keep from JAX model
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.
169
-
170
- Returns:
171
- eqx.Module: Converted Equinox model in inference mode
172
- """
173
- dynamic, static = eqx.partition(jax_model, eqx.is_array)
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)
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