hyper-models 0.1.0__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 (27) hide show
  1. {hyper_models-0.1.0 → hyper_models-0.3.0}/PKG-INFO +85 -10
  2. hyper_models-0.3.0/README.md +170 -0
  3. {hyper_models-0.1.0 → hyper_models-0.3.0}/pyproject.toml +12 -3
  4. hyper_models-0.3.0/src/hyper3_clip/__init__.py +3 -0
  5. hyper_models-0.3.0/src/hyper3_clip/models/__init__.py +3 -0
  6. hyper_models-0.3.0/src/hyper3_clip/models/encoders.py +181 -0
  7. hyper_models-0.3.0/src/hyper3_clip/models/experimental.py +691 -0
  8. hyper_models-0.3.0/src/hyper3_clip/models/himo.py +54 -0
  9. hyper_models-0.3.0/src/hyper3_clip/models/hyper3_clip.py +1032 -0
  10. hyper_models-0.3.0/src/hyper3_clip/models/lorentz.py +285 -0
  11. hyper_models-0.3.0/src/hyper3_clip/models/losses.py +1546 -0
  12. hyper_models-0.3.0/src/hyper3_clip/models/objectives.py +653 -0
  13. hyper_models-0.3.0/src/hyper3_clip/models/tren.py +282 -0
  14. hyper_models-0.3.0/src/hyper3_clip/training/__init__.py +1 -0
  15. hyper_models-0.3.0/src/hyper3_clip/training/distributed.py +153 -0
  16. {hyper_models-0.1.0 → hyper_models-0.3.0}/src/hyper_models/__init__.py +6 -4
  17. hyper_models-0.3.0/src/hyper_models/loader.py +48 -0
  18. hyper_models-0.3.0/src/hyper_models/loaders.py +84 -0
  19. hyper_models-0.3.0/src/hyper_models/registry.py +145 -0
  20. hyper_models-0.3.0/src/hyper_models/torch_models.py +328 -0
  21. hyper_models-0.1.0/README.md +0 -103
  22. hyper_models-0.1.0/src/hyper_models/loader.py +0 -45
  23. hyper_models-0.1.0/src/hyper_models/registry.py +0 -78
  24. {hyper_models-0.1.0 → hyper_models-0.3.0}/.gitignore +0 -0
  25. {hyper_models-0.1.0 → hyper_models-0.3.0}/LICENSE +0 -0
  26. {hyper_models-0.1.0 → hyper_models-0.3.0}/src/hyper_models/models.py +0 -0
  27. {hyper_models-0.1.0 → hyper_models-0.3.0}/src/hyper_models/preprocessing.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyper-models
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: A model zoo for non-Euclidean embedding models (hyperbolic, spherical, product manifolds)
5
5
  Project-URL: Homepage, https://github.com/Hyper3Labs/hyper-models
6
6
  Project-URL: Repository, https://github.com/Hyper3Labs/hyper-models
7
7
  Project-URL: Documentation, https://github.com/Hyper3Labs/hyper-models#readme
8
8
  Project-URL: Issues, https://github.com/Hyper3Labs/hyper-models/issues
9
- Author: Hyper3Labs
9
+ Author: hyper3labs
10
10
  License: MIT
11
11
  License-File: LICENSE
12
12
  Keywords: clip,embeddings,hyperbolic,model-zoo,non-euclidean,onnx
@@ -27,6 +27,14 @@ Requires-Dist: pillow>=10.0
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=7.0; extra == 'dev'
29
29
  Requires-Dist: ruff>=0.1; extra == 'dev'
30
+ Provides-Extra: ml
31
+ Requires-Dist: protobuf>=4.25.0; extra == 'ml'
32
+ Requires-Dist: pyyaml>=6.0.0; extra == 'ml'
33
+ Requires-Dist: safetensors>=0.4.0; extra == 'ml'
34
+ Requires-Dist: sentencepiece>=0.2.0; extra == 'ml'
35
+ Requires-Dist: timm>=1.0.0; extra == 'ml'
36
+ Requires-Dist: torch>=2.9.1; extra == 'ml'
37
+ Requires-Dist: transformers<6.0,>=5.0.0; extra == 'ml'
30
38
  Description-Content-Type: text/markdown
31
39
 
32
40
  # hyper-models
@@ -51,13 +59,22 @@ Description-Content-Type: text/markdown
51
59
  ## Why?
52
60
 
53
61
  - **Standardized access** to non-Euclidean embedding models
54
- - **Torch-free runtime** via ONNX (models published to Hugging Face Hub)
62
+ - **One catalog surface**: model names map to internal loaders such as ONNX or optional torch-backed runtimes
55
63
  - **Simple API** — `load()` and `encode_images()`
56
64
 
57
65
  ## Installation
58
66
 
59
67
  ```bash
60
- pip install hyper-models
68
+ uv pip install hyper-models
69
+ ```
70
+
71
+ This base install is the simple path: it stays **torch-free** and is enough for
72
+ ONNX-backed catalog entries such as HyCoCLIP and MERU.
73
+
74
+ For torch-backed checkpoints (for example UNCHA and Hyper3-CLIP):
75
+
76
+ ```bash
77
+ uv pip install "hyper-models[ml]"
61
78
  ```
62
79
 
63
80
  ## Usage
@@ -68,7 +85,11 @@ from PIL import Image
68
85
 
69
86
  # List available models
70
87
  hyper_models.list_models()
71
- # ['hycoclip-vit-s', 'hycoclip-vit-b', 'meru-vit-s', 'meru-vit-b']
88
+ # ['hycoclip-vit-s', 'hycoclip-vit-b', 'meru-vit-s', 'meru-vit-b', 'uncha-vit-s', 'uncha-vit-b', 'hyper3-clip-v0.5']
89
+
90
+ # Inspect supported internal loader kinds
91
+ hyper_models.list_loaders()
92
+ # ['hyper3-clip-torch', 'onnx', 'uncha-image-torch']
72
93
 
73
94
  # Load model (auto-downloads from Hugging Face Hub)
74
95
  model = hyper_models.load("hycoclip-vit-s")
@@ -82,6 +103,7 @@ embeddings = model.encode_images(images) # (1, 513) ndarray
82
103
  # Get model info
83
104
  info = hyper_models.get_model_info("hycoclip-vit-s")
84
105
  info.hub_id # 'mnm-matin/hyperbolic-clip'
106
+ info.loader # 'onnx'
85
107
  info.license # 'CC-BY-NC'
86
108
 
87
109
  # Low-level: preprocess images yourself
@@ -89,6 +111,54 @@ batch = hyper_models.preprocess_images(images) # (B, 3, 224, 224)
89
111
  embeddings = model.encode(batch)
90
112
  ```
91
113
 
114
+ ### Architecture
115
+
116
+ `hyper-models` is intended to be a timm-like catalog for non-Euclidean models.
117
+
118
+ - The public abstraction is the catalog entry name, for example `hycoclip-vit-s`.
119
+ - Each entry declares metadata such as geometry, dimensionality, artifact path,
120
+ and an internal loader kind.
121
+ - Internal loaders may differ by model family:
122
+ - `onnx` for exported, torch-free runtimes
123
+ - `uncha-image-torch` for raw checkpoints that need a PyTorch image runtime
124
+ - `hyper3-clip-torch` for Hyper3-CLIP safetensors checkpoints
125
+
126
+ This keeps callers on one stable API:
127
+
128
+ ```python
129
+ model = hyper_models.load("hycoclip-vit-s")
130
+ model = hyper_models.load("uncha-vit-b")
131
+ model = hyper_models.load("hyper3-clip-v0.5")
132
+ ```
133
+
134
+ Callers do not need to know which internal loader is used, except for optional
135
+ dependency installation when choosing entries that need `hyper-models[ml]`.
136
+
137
+ ### HyperView integration
138
+
139
+ HyperView auto-detects `hyper-models` names and routes them to the `hyper-models` provider.
140
+
141
+ ```python
142
+ import hyperview as hv
143
+
144
+ dataset = hv.Dataset.from_huggingface(
145
+ name="demo",
146
+ hf_dataset="uoft-cs/cifar10",
147
+ split="train",
148
+ image_key="img",
149
+ )
150
+
151
+ # Uses provider='hyper-models' automatically.
152
+ space_key = dataset.compute_embeddings(model="uncha-vit-b")
153
+ layout_key = dataset.compute_visualization(space_key=space_key, layout="poincare")
154
+ ```
155
+
156
+ HyperView's simple path remains torch-free. If you use the default ONNX-backed
157
+ `hyper-models` entries or the default `embed-anything` provider, HyperView does
158
+ not need PyTorch. PyTorch is only needed when you explicitly select a
159
+ torch-backed catalog entry such as `uncha-vit-s`, `uncha-vit-b`, or
160
+ `hyper3-clip-v0.5`.
161
+
92
162
  ## Models
93
163
 
94
164
  ### Hyperbolic
@@ -99,16 +169,21 @@ embeddings = model.encode(batch)
99
169
  | `hycoclip-vit-b` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/hycoclip-vit-b) | [ICLR 2025](https://arxiv.org/abs/2410.06912) | [PalAvik/hycoclip](https://github.com/PalAvik/hycoclip) |
100
170
  | `meru-vit-s` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/meru-vit-s) | [ICML 2023](https://arxiv.org/abs/2304.09172) | [facebookresearch/meru](https://github.com/facebookresearch/meru) |
101
171
  | `meru-vit-b` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/meru-vit-b) | [ICML 2023](https://arxiv.org/abs/2304.09172) | [facebookresearch/meru](https://github.com/facebookresearch/meru) |
172
+ | `uncha-vit-s` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/hayeonkim/uncha/blob/main/uncha_vit_s.pth) | [CVPR 2026](https://arxiv.org/abs/2603.22042) | [jeeit17/UNCHA](https://github.com/jeeit17/UNCHA) |
173
+ | `uncha-vit-b` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/hayeonkim/uncha/blob/main/uncha_vit_b.pth) | [CVPR 2026](https://arxiv.org/abs/2603.22042) | [jeeit17/UNCHA](https://github.com/jeeit17/UNCHA) |
174
+ | `hyper3-clip-v0.5` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/hyper3labs/hyper3-clip-v0.5) | — | [Hyper3Labs/hyper3-clip](https://github.com/Hyper3Labs/hyper3-clip) |
102
175
  | `hyp-vit` | — | [CVPR 2022](https://arxiv.org/abs/2203.10833) | [htdt/hyp_metric](https://github.com/htdt/hyp_metric) |
103
176
  | `hie` | — | [CVPR 2020](https://arxiv.org/abs/1904.02239) | [leymir/hyperbolic-image-embeddings](https://github.com/leymir/hyperbolic-image-embeddings) |
104
177
  | `hcnn` | — | [ICLR 2024](https://openreview.net/forum?id=ekz1hN5QNh) | [kschwethelm/HyperbolicCV](https://github.com/kschwethelm/HyperbolicCV) |
105
178
 
106
- ### Spherical
179
+ ### Hyperspherical
180
+
181
+ | Model | Available | Paper | Code |
182
+ |------------------|:---------:|-------|------|
183
+ | `megadescriptor` (via timm) | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/BVRA/MegaDescriptor-L-384) | [WACV 2024](https://openaccess.thecvf.com/content/WACV2024/papers/Cermak_WildlifeDatasets_An_Open-Source_Toolkit_for_Animal_Re-Identification_WACV_2024_paper.pdf) | [WildlifeDatasets/wildlife-datasets](https://github.com/WildlifeDatasets/wildlife-datasets) |
184
+ | `sphereface` | — | [CVPR 2017](https://arxiv.org/abs/1704.08063) | [wy1iu/sphereface](https://github.com/wy1iu/sphereface) |
185
+ | `arcface` | — | [CVPR 2019](https://arxiv.org/abs/1801.07698) | [deepinsight/insightface](https://github.com/deepinsight/insightface) |
107
186
 
108
- | Model | Available | Paper | Code |
109
- |-------|:---------:|-------|------|
110
- | `sphereface` | — | [CVPR 2017](https://arxiv.org/abs/1704.08063) | [wy1iu/sphereface](https://github.com/wy1iu/sphereface) |
111
- | `arcface` | — | [CVPR 2019](https://arxiv.org/abs/1801.07698) | [deepinsight/insightface](https://github.com/deepinsight/insightface) |
112
187
 
113
188
  ### Product Manifolds
114
189
 
@@ -0,0 +1,170 @@
1
+ # hyper-models
2
+
3
+ <p align="center">
4
+ <strong>A model zoo for non-Euclidean embedding models</strong>
5
+ <br>
6
+ <em>Hyperbolic · Spherical · Product Manifolds</em>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://huggingface.co/mnm-matin/hyperbolic-clip">
11
+ <img src="https://img.shields.io/badge/🤗_Models-hyperbolic--clip-orange" alt="Hugging Face">
12
+ </a>
13
+ <a href="LICENSE">
14
+ <img src="https://img.shields.io/badge/License-MIT-blue" alt="License: MIT">
15
+ </a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## Why?
21
+
22
+ - **Standardized access** to non-Euclidean embedding models
23
+ - **One catalog surface**: model names map to internal loaders such as ONNX or optional torch-backed runtimes
24
+ - **Simple API** — `load()` and `encode_images()`
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ uv pip install hyper-models
30
+ ```
31
+
32
+ This base install is the simple path: it stays **torch-free** and is enough for
33
+ ONNX-backed catalog entries such as HyCoCLIP and MERU.
34
+
35
+ For torch-backed checkpoints (for example UNCHA and Hyper3-CLIP):
36
+
37
+ ```bash
38
+ uv pip install "hyper-models[ml]"
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```python
44
+ import hyper_models
45
+ from PIL import Image
46
+
47
+ # List available models
48
+ hyper_models.list_models()
49
+ # ['hycoclip-vit-s', 'hycoclip-vit-b', 'meru-vit-s', 'meru-vit-b', 'uncha-vit-s', 'uncha-vit-b', 'hyper3-clip-v0.5']
50
+
51
+ # Inspect supported internal loader kinds
52
+ hyper_models.list_loaders()
53
+ # ['hyper3-clip-torch', 'onnx', 'uncha-image-torch']
54
+
55
+ # Load model (auto-downloads from Hugging Face Hub)
56
+ model = hyper_models.load("hycoclip-vit-s")
57
+ model.geometry # 'hyperboloid'
58
+ model.dim # 513
59
+
60
+ # Encode PIL images
61
+ images = [Image.open("image.jpg")]
62
+ embeddings = model.encode_images(images) # (1, 513) ndarray
63
+
64
+ # Get model info
65
+ info = hyper_models.get_model_info("hycoclip-vit-s")
66
+ info.hub_id # 'mnm-matin/hyperbolic-clip'
67
+ info.loader # 'onnx'
68
+ info.license # 'CC-BY-NC'
69
+
70
+ # Low-level: preprocess images yourself
71
+ batch = hyper_models.preprocess_images(images) # (B, 3, 224, 224)
72
+ embeddings = model.encode(batch)
73
+ ```
74
+
75
+ ### Architecture
76
+
77
+ `hyper-models` is intended to be a timm-like catalog for non-Euclidean models.
78
+
79
+ - The public abstraction is the catalog entry name, for example `hycoclip-vit-s`.
80
+ - Each entry declares metadata such as geometry, dimensionality, artifact path,
81
+ and an internal loader kind.
82
+ - Internal loaders may differ by model family:
83
+ - `onnx` for exported, torch-free runtimes
84
+ - `uncha-image-torch` for raw checkpoints that need a PyTorch image runtime
85
+ - `hyper3-clip-torch` for Hyper3-CLIP safetensors checkpoints
86
+
87
+ This keeps callers on one stable API:
88
+
89
+ ```python
90
+ model = hyper_models.load("hycoclip-vit-s")
91
+ model = hyper_models.load("uncha-vit-b")
92
+ model = hyper_models.load("hyper3-clip-v0.5")
93
+ ```
94
+
95
+ Callers do not need to know which internal loader is used, except for optional
96
+ dependency installation when choosing entries that need `hyper-models[ml]`.
97
+
98
+ ### HyperView integration
99
+
100
+ HyperView auto-detects `hyper-models` names and routes them to the `hyper-models` provider.
101
+
102
+ ```python
103
+ import hyperview as hv
104
+
105
+ dataset = hv.Dataset.from_huggingface(
106
+ name="demo",
107
+ hf_dataset="uoft-cs/cifar10",
108
+ split="train",
109
+ image_key="img",
110
+ )
111
+
112
+ # Uses provider='hyper-models' automatically.
113
+ space_key = dataset.compute_embeddings(model="uncha-vit-b")
114
+ layout_key = dataset.compute_visualization(space_key=space_key, layout="poincare")
115
+ ```
116
+
117
+ HyperView's simple path remains torch-free. If you use the default ONNX-backed
118
+ `hyper-models` entries or the default `embed-anything` provider, HyperView does
119
+ not need PyTorch. PyTorch is only needed when you explicitly select a
120
+ torch-backed catalog entry such as `uncha-vit-s`, `uncha-vit-b`, or
121
+ `hyper3-clip-v0.5`.
122
+
123
+ ## Models
124
+
125
+ ### Hyperbolic
126
+
127
+ | Model | Available | Paper | Code |
128
+ |-------|:---------:|-------|------|
129
+ | `hycoclip-vit-s` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/hycoclip-vit-s) | [ICLR 2025](https://arxiv.org/abs/2410.06912) | [PalAvik/hycoclip](https://github.com/PalAvik/hycoclip) |
130
+ | `hycoclip-vit-b` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/hycoclip-vit-b) | [ICLR 2025](https://arxiv.org/abs/2410.06912) | [PalAvik/hycoclip](https://github.com/PalAvik/hycoclip) |
131
+ | `meru-vit-s` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/meru-vit-s) | [ICML 2023](https://arxiv.org/abs/2304.09172) | [facebookresearch/meru](https://github.com/facebookresearch/meru) |
132
+ | `meru-vit-b` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/mnm-matin/hyperbolic-clip/tree/main/meru-vit-b) | [ICML 2023](https://arxiv.org/abs/2304.09172) | [facebookresearch/meru](https://github.com/facebookresearch/meru) |
133
+ | `uncha-vit-s` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/hayeonkim/uncha/blob/main/uncha_vit_s.pth) | [CVPR 2026](https://arxiv.org/abs/2603.22042) | [jeeit17/UNCHA](https://github.com/jeeit17/UNCHA) |
134
+ | `uncha-vit-b` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/hayeonkim/uncha/blob/main/uncha_vit_b.pth) | [CVPR 2026](https://arxiv.org/abs/2603.22042) | [jeeit17/UNCHA](https://github.com/jeeit17/UNCHA) |
135
+ | `hyper3-clip-v0.5` | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/hyper3labs/hyper3-clip-v0.5) | — | [Hyper3Labs/hyper3-clip](https://github.com/Hyper3Labs/hyper3-clip) |
136
+ | `hyp-vit` | — | [CVPR 2022](https://arxiv.org/abs/2203.10833) | [htdt/hyp_metric](https://github.com/htdt/hyp_metric) |
137
+ | `hie` | — | [CVPR 2020](https://arxiv.org/abs/1904.02239) | [leymir/hyperbolic-image-embeddings](https://github.com/leymir/hyperbolic-image-embeddings) |
138
+ | `hcnn` | — | [ICLR 2024](https://openreview.net/forum?id=ekz1hN5QNh) | [kschwethelm/HyperbolicCV](https://github.com/kschwethelm/HyperbolicCV) |
139
+
140
+ ### Hyperspherical
141
+
142
+ | Model | Available | Paper | Code |
143
+ |------------------|:---------:|-------|------|
144
+ | `megadescriptor` (via timm) | [![HF](https://img.shields.io/badge/🤗-HuggingFace-yellow)](https://huggingface.co/BVRA/MegaDescriptor-L-384) | [WACV 2024](https://openaccess.thecvf.com/content/WACV2024/papers/Cermak_WildlifeDatasets_An_Open-Source_Toolkit_for_Animal_Re-Identification_WACV_2024_paper.pdf) | [WildlifeDatasets/wildlife-datasets](https://github.com/WildlifeDatasets/wildlife-datasets) |
145
+ | `sphereface` | — | [CVPR 2017](https://arxiv.org/abs/1704.08063) | [wy1iu/sphereface](https://github.com/wy1iu/sphereface) |
146
+ | `arcface` | — | [CVPR 2019](https://arxiv.org/abs/1801.07698) | [deepinsight/insightface](https://github.com/deepinsight/insightface) |
147
+
148
+
149
+ ### Product Manifolds
150
+
151
+ | Model | Available | Paper | Code |
152
+ |-------|:---------:|-------|------|
153
+ | `hyperbolics` | — | [ICLR 2019](https://openreview.net/forum?id=HJxeWnCcF7) | [HazyResearch/hyperbolics](https://github.com/HazyResearch/hyperbolics) |
154
+
155
+ ## Export Tooling
156
+
157
+ This repo also contains tooling to export PyTorch models to ONNX:
158
+
159
+ ```bash
160
+ cd export/hycoclip
161
+ uv run python export_onnx.py --checkpoint model.pth --onnx model.onnx
162
+ ```
163
+
164
+ See [export/hycoclip/README.md](export/hycoclip/README.md) for details.
165
+
166
+ ## References
167
+
168
+ - [HyCoCLIP](https://github.com/PalAvik/hycoclip)
169
+ - [MERU](https://github.com/facebookresearch/meru)
170
+ - [geoopt](https://github.com/geoopt/geoopt)
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "hyper-models"
3
- version = "0.1.0"
3
+ version = "0.3.0"
4
4
  description = "A model zoo for non-Euclidean embedding models (hyperbolic, spherical, product manifolds)"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
7
7
  requires-python = ">=3.10"
8
- authors = [{ name = "Hyper3Labs" }]
8
+ authors = [{ name = "hyper3labs" }]
9
9
  keywords = ["embeddings", "hyperbolic", "onnx", "clip", "model-zoo", "non-euclidean"]
10
10
  classifiers = [
11
11
  "Development Status :: 3 - Alpha",
@@ -30,6 +30,15 @@ dev = [
30
30
  "pytest>=7.0",
31
31
  "ruff>=0.1",
32
32
  ]
33
+ ml = [
34
+ "torch>=2.9.1",
35
+ "timm>=1.0.0",
36
+ "transformers>=5.0.0,<6.0",
37
+ "safetensors>=0.4.0",
38
+ "pyyaml>=6.0.0",
39
+ "sentencepiece>=0.2.0",
40
+ "protobuf>=4.25.0",
41
+ ]
33
42
 
34
43
  [project.urls]
35
44
  Homepage = "https://github.com/Hyper3Labs/hyper-models"
@@ -42,7 +51,7 @@ requires = ["hatchling"]
42
51
  build-backend = "hatchling.build"
43
52
 
44
53
  [tool.hatch.build.targets.wheel]
45
- packages = ["src/hyper_models"]
54
+ packages = ["src/hyper_models", "src/hyper3_clip"]
46
55
 
47
56
  [tool.hatch.build.targets.sdist]
48
57
  include = ["/src", "/README.md", "/LICENSE"]
@@ -0,0 +1,3 @@
1
+ from hyper3_clip.models.hyper3_clip import Hyper3CLIP
2
+
3
+ __all__ = ["Hyper3CLIP"]
@@ -0,0 +1,3 @@
1
+ from hyper3_clip.models.hyper3_clip import Hyper3CLIP
2
+
3
+ __all__ = ["Hyper3CLIP"]
@@ -0,0 +1,181 @@
1
+ from __future__ import annotations
2
+
3
+ import timm
4
+ import torch
5
+ from torch import nn
6
+ from transformers import (
7
+ AutoConfig,
8
+ AutoModel,
9
+ AutoTokenizer,
10
+ CLIPTextConfig,
11
+ CLIPTextModel,
12
+ CLIPTextModelWithProjection,
13
+ CLIPVisionConfig,
14
+ CLIPVisionModel,
15
+ CLIPVisionModelWithProjection,
16
+ SiglipTextConfig,
17
+ SiglipTextModel,
18
+ SiglipVisionConfig,
19
+ SiglipVisionModel,
20
+ )
21
+
22
+
23
+ class VisionEncoder(nn.Module):
24
+ def __init__(self, backbone_name: str, pretrained: bool = True) -> None:
25
+ super().__init__()
26
+ self.kind = "timm"
27
+ if backbone_name.startswith("hf_clip_projected:"):
28
+ self.kind = "hf_clip_projected"
29
+ model_name = backbone_name.removeprefix("hf_clip_projected:")
30
+ self.backbone = (
31
+ CLIPVisionModelWithProjection.from_pretrained(model_name)
32
+ if pretrained
33
+ else CLIPVisionModelWithProjection(CLIPVisionConfig.from_pretrained(model_name))
34
+ )
35
+ self.output_dim = self.backbone.config.projection_dim
36
+ elif backbone_name.startswith("hf_clip:"):
37
+ self.kind = "hf_vision"
38
+ model_name = backbone_name.removeprefix("hf_clip:")
39
+ self.backbone = (
40
+ CLIPVisionModel.from_pretrained(model_name)
41
+ if pretrained
42
+ else CLIPVisionModel(CLIPVisionConfig.from_pretrained(model_name))
43
+ )
44
+ self.output_dim = self.backbone.config.hidden_size
45
+ elif backbone_name.startswith("hf_siglip:"):
46
+ self.kind = "hf_vision"
47
+ model_name = backbone_name.removeprefix("hf_siglip:")
48
+ self.backbone = (
49
+ SiglipVisionModel.from_pretrained(model_name)
50
+ if pretrained
51
+ else SiglipVisionModel(SiglipVisionConfig.from_pretrained(model_name))
52
+ )
53
+ self.output_dim = self.backbone.config.hidden_size
54
+ else:
55
+ self.backbone = timm.create_model(
56
+ backbone_name,
57
+ pretrained=pretrained,
58
+ num_classes=0,
59
+ global_pool="avg",
60
+ )
61
+ self.output_dim = self.backbone.num_features
62
+
63
+ def forward(self, image: torch.Tensor) -> torch.Tensor:
64
+ if self.kind == "hf_clip_projected":
65
+ return self.backbone(pixel_values=image).image_embeds
66
+ if self.kind == "hf_vision":
67
+ out = self.backbone(pixel_values=image)
68
+ if hasattr(out, "pooler_output") and out.pooler_output is not None:
69
+ return out.pooler_output
70
+ return out.last_hidden_state[:, 0]
71
+ return self.backbone(image)
72
+
73
+ def forward_with_tokens(self, image: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
74
+ if self.kind == "hf_clip_projected":
75
+ out = self.backbone(pixel_values=image)
76
+ tokens = getattr(out, "last_hidden_state", None)
77
+ if tokens is None and hasattr(out, "vision_model_output"):
78
+ tokens = out.vision_model_output.last_hidden_state
79
+ if tokens is None:
80
+ raise RuntimeError("Projected CLIP vision output did not include patch tokens")
81
+ return out.image_embeds, tokens
82
+ if self.kind == "hf_vision":
83
+ out = self.backbone(pixel_values=image)
84
+ if hasattr(out, "pooler_output") and out.pooler_output is not None:
85
+ pooled = out.pooler_output
86
+ else:
87
+ pooled = out.last_hidden_state[:, 0]
88
+ return pooled, out.last_hidden_state
89
+
90
+ if not hasattr(self.backbone, "forward_features"):
91
+ pooled = self.backbone(image)
92
+ return pooled, pooled[:, None, :]
93
+ features = self.backbone.forward_features(image)
94
+ if hasattr(self.backbone, "forward_head"):
95
+ pooled = self.backbone.forward_head(features, pre_logits=False)
96
+ else:
97
+ pooled = self.backbone(image)
98
+ return pooled, _tokens_from_features(features)
99
+
100
+
101
+ class TextEncoder(nn.Module):
102
+ def __init__(self, model_name: str, pretrained: bool = True, pooling: str = "auto") -> None:
103
+ super().__init__()
104
+ if pooling not in {"auto", "pooler", "cls", "mean"}:
105
+ raise ValueError(
106
+ f"Unsupported text pooling {pooling!r}; expected auto, pooler, cls, or mean"
107
+ )
108
+ self.kind = "hf_text"
109
+ self.pooling = pooling
110
+ tokenizer_name = model_name.removeprefix("hf_clip_projected:")
111
+ self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name)
112
+ model_name_lower = model_name.lower()
113
+ if model_name.startswith("hf_clip_projected:"):
114
+ self.kind = "hf_clip_projected"
115
+ projected_model_name = model_name.removeprefix("hf_clip_projected:")
116
+ if pretrained:
117
+ self.backbone = CLIPTextModelWithProjection.from_pretrained(projected_model_name)
118
+ else:
119
+ self.backbone = CLIPTextModelWithProjection(
120
+ CLIPTextConfig.from_pretrained(projected_model_name)
121
+ )
122
+ self.output_dim = self.backbone.config.projection_dim
123
+ elif "siglip" in model_name_lower:
124
+ if pretrained:
125
+ self.backbone = SiglipTextModel.from_pretrained(model_name)
126
+ else:
127
+ self.backbone = SiglipTextModel(SiglipTextConfig.from_pretrained(model_name))
128
+ self.output_dim = self.backbone.config.hidden_size
129
+ elif "clip" in model_name_lower:
130
+ if pretrained:
131
+ self.backbone = CLIPTextModel.from_pretrained(model_name)
132
+ else:
133
+ self.backbone = CLIPTextModel(CLIPTextConfig.from_pretrained(model_name))
134
+ self.output_dim = self.backbone.config.hidden_size
135
+ else:
136
+ if pretrained:
137
+ self.backbone = AutoModel.from_pretrained(model_name)
138
+ else:
139
+ self.backbone = AutoModel.from_config(AutoConfig.from_pretrained(model_name))
140
+ hidden_size = getattr(self.backbone.config, "hidden_size", None)
141
+ if hidden_size is None:
142
+ raise ValueError(f"Unsupported text model config for {model_name}")
143
+ self.output_dim = hidden_size
144
+
145
+ def forward(self, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
146
+ out = self.backbone(input_ids=input_ids, attention_mask=attention_mask)
147
+ if self.kind == "hf_clip_projected":
148
+ return out.text_embeds
149
+ if self.pooling == "mean":
150
+ mask = attention_mask.to(dtype=out.last_hidden_state.dtype).unsqueeze(-1)
151
+ summed = (out.last_hidden_state * mask).sum(dim=1)
152
+ denom = mask.sum(dim=1).clamp_min(1.0)
153
+ return summed / denom
154
+ if (
155
+ self.pooling in {"auto", "pooler"}
156
+ and hasattr(out, "pooler_output")
157
+ and out.pooler_output is not None
158
+ ):
159
+ return out.pooler_output
160
+ return out.last_hidden_state[:, 0]
161
+
162
+
163
+ def _tokens_from_features(features: torch.Tensor | dict | tuple | list) -> torch.Tensor:
164
+ if isinstance(features, dict):
165
+ for key in ("x", "last_hidden_state", "features"):
166
+ if key in features:
167
+ features = features[key]
168
+ break
169
+ else:
170
+ features = next(iter(features.values()))
171
+ if isinstance(features, tuple | list):
172
+ features = features[0]
173
+ if not torch.is_tensor(features):
174
+ raise TypeError(f"Expected tensor features, got {type(features)!r}")
175
+ if features.ndim == 4:
176
+ return features.flatten(2).transpose(1, 2)
177
+ if features.ndim == 3:
178
+ return features
179
+ if features.ndim == 2:
180
+ return features[:, None, :]
181
+ raise ValueError(f"Unsupported feature tensor shape {tuple(features.shape)}")