hyper-models 0.3.2__tar.gz → 0.3.4__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 (24) hide show
  1. {hyper_models-0.3.2 → hyper_models-0.3.4}/PKG-INFO +14 -9
  2. {hyper_models-0.3.2 → hyper_models-0.3.4}/README.md +13 -8
  3. {hyper_models-0.3.2 → hyper_models-0.3.4}/pyproject.toml +1 -1
  4. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/encoders.py +21 -4
  5. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/hyper3_clip.py +7 -1
  6. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/models.py +17 -3
  7. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/preprocessing.py +13 -8
  8. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/registry.py +10 -2
  9. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/torch_models.py +45 -5
  10. {hyper_models-0.3.2 → hyper_models-0.3.4}/.gitignore +0 -0
  11. {hyper_models-0.3.2 → hyper_models-0.3.4}/LICENSE +0 -0
  12. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/__init__.py +0 -0
  13. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/__init__.py +0 -0
  14. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/experimental.py +0 -0
  15. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/himo.py +0 -0
  16. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/lorentz.py +0 -0
  17. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/losses.py +0 -0
  18. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/objectives.py +0 -0
  19. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/tren.py +0 -0
  20. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/training/__init__.py +0 -0
  21. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/training/distributed.py +0 -0
  22. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/__init__.py +0 -0
  23. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/loader.py +0 -0
  24. {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/loaders.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hyper-models
3
- Version: 0.3.2
3
+ Version: 0.3.4
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
@@ -96,7 +96,7 @@ hyper_models.list_loaders()
96
96
  # Load model (auto-downloads from Hugging Face Hub)
97
97
  model = hyper_models.load("hycoclip-vit-s")
98
98
  model.geometry # 'hyperboloid'
99
- model.dim # 513
99
+ model.dim # 513
100
100
 
101
101
  # Encode PIL images
102
102
  images = [Image.open("image.jpg")]
@@ -104,9 +104,9 @@ embeddings = model.encode_images(images) # (1, 513) ndarray
104
104
 
105
105
  # Get model info
106
106
  info = hyper_models.get_model_info("hycoclip-vit-s")
107
- info.hub_id # 'mnm-matin/hyperbolic-clip'
108
- info.loader # 'onnx'
109
- info.license # 'CC-BY-NC'
107
+ info.hub_id # 'mnm-matin/hyperbolic-clip'
108
+ info.loader # 'onnx'
109
+ info.license # 'CC-BY-NC'
110
110
 
111
111
  # Low-level: preprocess images yourself
112
112
  batch = hyper_models.preprocess_images(images) # (B, 3, 224, 224)
@@ -136,6 +136,11 @@ model = hyper_models.load("hyper3-clip-v1")
136
136
  Callers do not need to know which internal loader is used, except for optional
137
137
  dependency installation when choosing entries that need `hyper-models[ml]`.
138
138
 
139
+ For `hyper3-clip-v1`, `encode_images(images)` and `encode_texts(texts)` return
140
+ 513-coordinate Lorentz embeddings in the same space. The loader downloads the
141
+ model's runtime configuration, weights, and tokenizer together. Complete the
142
+ model's Hugging Face access form and run `hf auth login` before the first download.
143
+
139
144
  ### HyperView integration
140
145
 
141
146
  HyperView auto-detects `hyper-models` names and routes them to the `hyper-models` provider.
@@ -144,10 +149,10 @@ HyperView auto-detects `hyper-models` names and routes them to the `hyper-models
144
149
  import hyperview as hv
145
150
 
146
151
  dataset = hv.Dataset.from_huggingface(
147
- name="demo",
148
- hf_dataset="uoft-cs/cifar10",
149
- split="train",
150
- image_key="img",
152
+ name="demo",
153
+ hf_dataset="uoft-cs/cifar10",
154
+ split="train",
155
+ image_key="img",
151
156
  )
152
157
 
153
158
  # Uses provider='hyper-models' automatically.
@@ -55,7 +55,7 @@ hyper_models.list_loaders()
55
55
  # Load model (auto-downloads from Hugging Face Hub)
56
56
  model = hyper_models.load("hycoclip-vit-s")
57
57
  model.geometry # 'hyperboloid'
58
- model.dim # 513
58
+ model.dim # 513
59
59
 
60
60
  # Encode PIL images
61
61
  images = [Image.open("image.jpg")]
@@ -63,9 +63,9 @@ embeddings = model.encode_images(images) # (1, 513) ndarray
63
63
 
64
64
  # Get model info
65
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'
66
+ info.hub_id # 'mnm-matin/hyperbolic-clip'
67
+ info.loader # 'onnx'
68
+ info.license # 'CC-BY-NC'
69
69
 
70
70
  # Low-level: preprocess images yourself
71
71
  batch = hyper_models.preprocess_images(images) # (B, 3, 224, 224)
@@ -95,6 +95,11 @@ model = hyper_models.load("hyper3-clip-v1")
95
95
  Callers do not need to know which internal loader is used, except for optional
96
96
  dependency installation when choosing entries that need `hyper-models[ml]`.
97
97
 
98
+ For `hyper3-clip-v1`, `encode_images(images)` and `encode_texts(texts)` return
99
+ 513-coordinate Lorentz embeddings in the same space. The loader downloads the
100
+ model's runtime configuration, weights, and tokenizer together. Complete the
101
+ model's Hugging Face access form and run `hf auth login` before the first download.
102
+
98
103
  ### HyperView integration
99
104
 
100
105
  HyperView auto-detects `hyper-models` names and routes them to the `hyper-models` provider.
@@ -103,10 +108,10 @@ HyperView auto-detects `hyper-models` names and routes them to the `hyper-models
103
108
  import hyperview as hv
104
109
 
105
110
  dataset = hv.Dataset.from_huggingface(
106
- name="demo",
107
- hf_dataset="uoft-cs/cifar10",
108
- split="train",
109
- image_key="img",
111
+ name="demo",
112
+ hf_dataset="uoft-cs/cifar10",
113
+ split="train",
114
+ image_key="img",
110
115
  )
111
116
 
112
117
  # Uses provider='hyper-models' automatically.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyper-models"
3
- version = "0.3.2"
3
+ version = "0.3.4"
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" }
@@ -10,6 +10,7 @@ from transformers import (
10
10
  CLIPTextConfig,
11
11
  CLIPTextModel,
12
12
  CLIPTextModelWithProjection,
13
+ CLIPTokenizerFast,
13
14
  CLIPVisionConfig,
14
15
  CLIPVisionModel,
15
16
  CLIPVisionModelWithProjection,
@@ -99,7 +100,14 @@ class VisionEncoder(nn.Module):
99
100
 
100
101
 
101
102
  class TextEncoder(nn.Module):
102
- def __init__(self, model_name: str, pretrained: bool = True, pooling: str = "auto") -> None:
103
+ def __init__(
104
+ self,
105
+ model_name: str,
106
+ pretrained: bool = True,
107
+ pooling: str = "auto",
108
+ text_config: dict | None = None,
109
+ tokenizer_name_or_path: str | None = None,
110
+ ) -> None:
103
111
  super().__init__()
104
112
  if pooling not in {"auto", "pooler", "cls", "mean"}:
105
113
  raise ValueError(
@@ -107,10 +115,19 @@ class TextEncoder(nn.Module):
107
115
  )
108
116
  self.kind = "hf_text"
109
117
  self.pooling = pooling
110
- tokenizer_name = model_name.removeprefix("hf_clip_projected:")
111
- self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name)
118
+ tokenizer_name = tokenizer_name_or_path or model_name.removeprefix("hf_clip_projected:")
119
+ self.tokenizer = (
120
+ CLIPTokenizerFast.from_pretrained(tokenizer_name)
121
+ if text_config is not None
122
+ else AutoTokenizer.from_pretrained(tokenizer_name)
123
+ )
112
124
  model_name_lower = model_name.lower()
113
- if model_name.startswith("hf_clip_projected:"):
125
+ if text_config is not None:
126
+ if pretrained or text_config.get("model_type") != "clip_text_model":
127
+ raise ValueError("Bundled text_config requires a non-pretrained CLIP text encoder")
128
+ self.backbone = CLIPTextModel(CLIPTextConfig.from_dict(text_config))
129
+ self.output_dim = self.backbone.config.hidden_size
130
+ elif model_name.startswith("hf_clip_projected:"):
114
131
  self.kind = "hf_clip_projected"
115
132
  projected_model_name = model_name.removeprefix("hf_clip_projected:")
116
133
  if pretrained:
@@ -101,6 +101,8 @@ class Hyper3CLIP(ExperimentalObjectiveMixin, nn.Module):
101
101
  fuse_beta_query_encoder_forwards: bool = False,
102
102
  group_beta_query_pooling: bool = False,
103
103
  objective_autocast_dtype: str = "float32",
104
+ text_config: dict | None = None,
105
+ tokenizer_name_or_path: str | None = None,
104
106
  ) -> None:
105
107
  super().__init__()
106
108
  if objective not in {"hycoclip", "uncha", "proclip"}:
@@ -206,7 +208,11 @@ class Hyper3CLIP(ExperimentalObjectiveMixin, nn.Module):
206
208
  self.phyclip_num_factors = 0
207
209
  self.vision_encoder = VisionEncoder(vision_backbone, pretrained=vision_pretrained)
208
210
  self.text_encoder = TextEncoder(
209
- text_model_name, pretrained=text_pretrained, pooling=text_pooling
211
+ text_model_name,
212
+ pretrained=text_pretrained,
213
+ pooling=text_pooling,
214
+ text_config=text_config,
215
+ tokenizer_name_or_path=tokenizer_name_or_path,
210
216
  )
211
217
  self.tokenizer = self.text_encoder.tokenizer
212
218
  self.embed_dim = embed_dim
@@ -3,6 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
+ from shutil import copyfile
7
+ from tempfile import TemporaryDirectory
6
8
 
7
9
  import numpy as np
8
10
  from PIL import Image
@@ -32,14 +34,26 @@ class ONNXModel:
32
34
  self._output_name = output_name
33
35
  self._image_config = image_config or ImageConfig()
34
36
  self._session = None
37
+ self._artifact_directory = None
35
38
 
36
39
  def _ensure_session(self) -> None:
37
40
  if self._session is None:
38
41
  import onnxruntime as ort
39
42
 
40
- self._session = ort.InferenceSession(
41
- str(self._path), providers=["CPUExecutionProvider"]
42
- )
43
+ path = self._path
44
+ # Hub snapshots link the graph and external weights to different
45
+ # blob directories. New ONNX Runtime versions correctly reject
46
+ # external data outside the resolved graph directory. Materialize
47
+ # our catalog's graph and sidecars together, keeping validation on.
48
+ artifacts = [path, *path.parent.glob(f"{path.name}.*")]
49
+ if any(artifact.is_symlink() for artifact in artifacts):
50
+ self._artifact_directory = TemporaryDirectory(prefix="hyper-models-onnx-")
51
+ directory = Path(self._artifact_directory.name)
52
+ for artifact in artifacts:
53
+ if artifact.is_file():
54
+ copyfile(artifact, directory / artifact.name)
55
+ path = directory / path.name
56
+ self._session = ort.InferenceSession(str(path), providers=["CPUExecutionProvider"])
43
57
 
44
58
  def encode(self, inputs: np.ndarray) -> np.ndarray:
45
59
  """Encode preprocessed inputs (B, C, H, W) to embeddings (B, D)."""
@@ -27,6 +27,7 @@ class ImageConfig:
27
27
  rescale: float = 1.0 / 255.0
28
28
  mean: tuple[float, float, float] | None = None
29
29
  std: tuple[float, float, float] | None = None
30
+ resize_mode: Literal["center_crop", "squash"] = "center_crop"
30
31
 
31
32
 
32
33
  def preprocess_images(images: list[Image.Image], config: ImageConfig | None = None) -> np.ndarray:
@@ -47,14 +48,18 @@ def preprocess_images(images: list[Image.Image], config: ImageConfig | None = No
47
48
  if img.mode != "RGB":
48
49
  img = img.convert("RGB")
49
50
 
50
- # Resize shortest side, then center crop
51
- w, h = img.size
52
- scale = config.size / min(w, h)
53
- img = img.resize((int(round(w * scale)), int(round(h * scale))), resample=resample)
54
-
55
- w, h = img.size
56
- left, top = (w - config.size) // 2, (h - config.size) // 2
57
- img = img.crop((left, top, left + config.size, top + config.size))
51
+ if config.resize_mode == "squash":
52
+ img = img.resize((config.size, config.size), resample=resample)
53
+ elif config.resize_mode == "center_crop":
54
+ # Resize shortest side, then center crop.
55
+ w, h = img.size
56
+ scale = config.size / min(w, h)
57
+ img = img.resize((int(round(w * scale)), int(round(h * scale))), resample=resample)
58
+ w, h = img.size
59
+ left, top = (w - config.size) // 2, (h - config.size) // 2
60
+ img = img.crop((left, top, left + config.size, top + config.size))
61
+ else:
62
+ raise ValueError(f"Unknown resize mode: {config.resize_mode!r}")
58
63
 
59
64
  # To float32 CHW
60
65
  arr = np.asarray(img, dtype=np.float32) * config.rescale
@@ -106,8 +106,15 @@ _MODELS: dict[str, ModelInfo] = {
106
106
  dim=513,
107
107
  hub_id="hyper3labs/hyper3-clip-v1",
108
108
  hub_path="model.safetensors",
109
- hub_patterns=("config.yaml", "model.safetensors"),
110
- license="Unknown",
109
+ hub_patterns=(
110
+ "config.json",
111
+ "model.safetensors",
112
+ "tokenizer.json",
113
+ "tokenizer_config.json",
114
+ "vocab.json",
115
+ "merges.txt",
116
+ ),
117
+ license="OpenMDW-1.0",
111
118
  loader="hyper3-clip-torch",
112
119
  optional_dependencies=("ml",),
113
120
  modalities=("image", "text"),
@@ -115,6 +122,7 @@ _MODELS: dict[str, ModelInfo] = {
115
122
  image_config=ImageConfig(
116
123
  size=224,
117
124
  interpolation="bicubic",
125
+ resize_mode="squash",
118
126
  mean=(0.485, 0.456, 0.406),
119
127
  std=(0.229, 0.224, 0.225),
120
128
  ),
@@ -6,6 +6,8 @@ They do not create a separate public provider surface.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import json
10
+ from dataclasses import replace
9
11
  from pathlib import Path
10
12
  from typing import Any
11
13
 
@@ -257,7 +259,7 @@ def _align_text_tower_keys(state: dict[str, Any]) -> dict[str, Any]:
257
259
 
258
260
 
259
261
  class Hyper3ClipTorchModel:
260
- """Hyper3-CLIP catalog entry runtime using torch for image inference."""
262
+ """Hyper3-CLIP image/text inference returning native Lorentz embeddings."""
261
263
 
262
264
  def __init__(
263
265
  self,
@@ -269,11 +271,14 @@ class Hyper3ClipTorchModel:
269
271
  device: str | None = None,
270
272
  ) -> None:
271
273
  self._checkpoint_path = checkpoint_path
272
- self._config_path = checkpoint_path.with_name("config.yaml")
274
+ self._config_path = checkpoint_path.with_name("config.json")
275
+ if not self._config_path.is_file():
276
+ self._config_path = checkpoint_path.with_name("config.yaml")
273
277
  self.geometry = geometry
274
278
  self.dim = dim
275
279
  self._image_config = image_config or ImageConfig()
276
280
  self._device_override = device
281
+ self._max_text_length = 77
277
282
 
278
283
  self._torch = None
279
284
  self._device = None
@@ -316,11 +321,41 @@ class Hyper3ClipTorchModel:
316
321
  self._torch = torch
317
322
  self._device = self._resolve_device(torch)
318
323
 
319
- config = yaml.safe_load(self._config_path.read_text(encoding="utf-8"))
320
- model_config = dict(config["model"])
324
+ if self._config_path.suffix == ".json":
325
+ config = json.loads(self._config_path.read_text(encoding="utf-8"))
326
+ model_config = {
327
+ key: config[key]
328
+ for key in (
329
+ "vision_backbone",
330
+ "text_model_name",
331
+ "embed_dim",
332
+ "curv_init",
333
+ "learn_curv",
334
+ )
335
+ }
336
+ # These constructor arguments affect training losses only.
337
+ model_config.update(
338
+ entail_weight=0.0, inter_aperture_scale=0.0, intra_aperture_scale=0.0
339
+ )
340
+ model_config["text_config"] = config["text_config"]
341
+ model_config["tokenizer_name_or_path"] = str(self._checkpoint_path.parent)
342
+ else:
343
+ config = yaml.safe_load(self._config_path.read_text(encoding="utf-8"))
344
+ model_config = dict(config["model"])
345
+ data_config = config.get("data", config)
346
+ self._image_config = replace(
347
+ self._image_config,
348
+ size=int(data_config.get("image_size", self._image_config.size)),
349
+ resize_mode="squash",
350
+ )
351
+ self._max_text_length = int(data_config.get("max_text_length", 77))
321
352
  model_config["vision_pretrained"] = False
322
353
  model_config["text_pretrained"] = False
323
354
  model = Hyper3CLIP(**model_config)
355
+ if model.embed_dim + 1 != self.dim:
356
+ raise ValueError("Hyper3-CLIP embedding dimension does not match the catalog entry")
357
+ model.curv_min = float(config.get("curvature_min", model.curv_min))
358
+ model.curv_max = float(config.get("curvature_max", model.curv_max))
324
359
  state = _align_text_tower_keys(load_file(self._checkpoint_path, device="cpu"))
325
360
  load_result = model.load_state_dict(state, strict=False)
326
361
  if load_result.missing_keys:
@@ -346,6 +381,7 @@ class Hyper3ClipTorchModel:
346
381
 
347
382
  def encode_images(self, images: list[Image.Image]) -> np.ndarray:
348
383
  """Encode PIL images to Hyper3-CLIP embeddings (B, D)."""
384
+ self._ensure_model()
349
385
  return self.encode(preprocess_images(images, self._image_config))
350
386
 
351
387
  def encode_texts(self, texts: list[str]) -> np.ndarray:
@@ -357,7 +393,11 @@ class Hyper3ClipTorchModel:
357
393
  assert self._device is not None
358
394
 
359
395
  encoded = self._model.tokenizer(
360
- list(texts), padding=True, truncation=True, return_tensors="pt"
396
+ list(texts),
397
+ padding=True,
398
+ truncation=True,
399
+ max_length=self._max_text_length,
400
+ return_tensors="pt",
361
401
  )
362
402
  input_ids = encoded["input_ids"].to(self._device)
363
403
  attention_mask = encoded["attention_mask"].to(self._device)
File without changes
File without changes