cortexgrid-infer 0.2.6__tar.gz → 0.2.7__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. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/PKG-INFO +4 -2
  2. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/README.md +1 -0
  3. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/__init__.py +12 -0
  4. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/core.py +27 -0
  5. cortexgrid_infer-0.2.7/cortexgrid_infer/meshing.py +72 -0
  6. cortexgrid_infer-0.2.7/cortexgrid_infer/meshing_serve.py +72 -0
  7. cortexgrid_infer-0.2.7/cortexgrid_infer/providers/huggingface_mesh.py +37 -0
  8. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/pyproject.toml +4 -2
  9. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/.gitignore +0 -0
  10. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/LICENSE +0 -0
  11. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/compiling.py +0 -0
  12. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/completion.py +0 -0
  13. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/device.py +0 -0
  14. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/importing.py +0 -0
  15. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/__init__.py +0 -0
  16. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/anthropic.py +0 -0
  17. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/anthropic_serve.py +0 -0
  18. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/huggingface_complete.py +0 -0
  19. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/huggingface_complete_serve.py +0 -0
  20. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/huggingface_image.py +0 -0
  21. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/providers/huggingface_image_serve.py +0 -0
  22. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/py.typed +0 -0
  23. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/requirements.py +0 -0
  24. {cortexgrid_infer-0.2.6 → cortexgrid_infer-0.2.7}/cortexgrid_infer/utils.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cortexgrid-infer
3
- Version: 0.2.6
4
- Summary: Deploy and interact with models across providers (Anthropic, HuggingFace LLM + diffusion)
3
+ Version: 0.2.7
4
+ Summary: Deploy and interact with models across providers (Anthropic, HuggingFace LLM + diffusion + image-to-mesh)
5
5
  Project-URL: Homepage, https://github.com/robodatalab/cortexgrid-infer
6
6
  Project-URL: Repository, https://github.com/robodatalab/cortexgrid-infer
7
7
  License-Expression: Apache-2.0
@@ -14,6 +14,7 @@ Requires-Dist: diffusers>=0.38
14
14
  Requires-Dist: fastapi>=0.133
15
15
  Requires-Dist: httpx>=0.27.0
16
16
  Requires-Dist: huggingface-hub>=0.20.0
17
+ Requires-Dist: numpy>=1.24
17
18
  Requires-Dist: pillow>=10.0
18
19
  Requires-Dist: protobuf>=4.0
19
20
  Requires-Dist: python-dotenv>=1.0.0
@@ -60,6 +61,7 @@ An **importer** is the whole of this library's cluster-side surface. It gives yo
60
61
  | `HuggingFaceCompletingImport(hf_id, token=None)` | HuggingFace causal LM, via `AutoModelForCausalLM` | `ServedCompletingModel` (`complete`) |
61
62
  | `HuggingFaceImageImport(hf_id, token=None, ignore_patterns=None)` | HuggingFace diffusers pipeline | `HuggingFaceImageModel` (`generate`) |
62
63
  | `AnthropicImport(model_id, api_key_secret=...)` | the Anthropic API, forwarded from the cluster | `ServedCompletingModel` (`complete`) |
64
+ | `HuggingFaceMeshImport(hf_id, token=None, ignore_patterns=None)` — a base: subclass it, naming a `MeshingDeployment` subclass as `serve_app` | an image-to-mesh model, run by the serve app the subclass supplies (`load` and `make_mesh`) behind the family's `POST /mesh` | `ServedMeshingModel` (`mesh`: one picture in, a `GeneratedMesh` out) |
63
65
 
64
66
  Not every model has weights, so `.source` belongs to the HuggingFace importers
65
67
  rather than to all of them: one that has weights goes through
@@ -34,6 +34,7 @@ An **importer** is the whole of this library's cluster-side surface. It gives yo
34
34
  | `HuggingFaceCompletingImport(hf_id, token=None)` | HuggingFace causal LM, via `AutoModelForCausalLM` | `ServedCompletingModel` (`complete`) |
35
35
  | `HuggingFaceImageImport(hf_id, token=None, ignore_patterns=None)` | HuggingFace diffusers pipeline | `HuggingFaceImageModel` (`generate`) |
36
36
  | `AnthropicImport(model_id, api_key_secret=...)` | the Anthropic API, forwarded from the cluster | `ServedCompletingModel` (`complete`) |
37
+ | `HuggingFaceMeshImport(hf_id, token=None, ignore_patterns=None)` — a base: subclass it, naming a `MeshingDeployment` subclass as `serve_app` | an image-to-mesh model, run by the serve app the subclass supplies (`load` and `make_mesh`) behind the family's `POST /mesh` | `ServedMeshingModel` (`mesh`: one picture in, a `GeneratedMesh` out) |
37
38
 
38
39
  Not every model has weights, so `.source` belongs to the HuggingFace importers
39
40
  rather than to all of them: one that has weights goes through
@@ -26,13 +26,16 @@ from cortexgrid_infer.core import (
26
26
  DeployedModel,
27
27
  CompletingModel,
28
28
  GeneratedImage,
29
+ GeneratedMesh,
29
30
  GeneratingModel,
31
+ MeshingModel,
30
32
  Message,
31
33
  Tool,
32
34
  ToolCall,
33
35
  ToolSpec,
34
36
  complete,
35
37
  generate,
38
+ mesh,
36
39
  )
37
40
  from cortexgrid_infer.completion import ServedCompletingModel
38
41
  from cortexgrid_infer.device import detect_device
@@ -43,19 +46,25 @@ from cortexgrid_infer.providers.huggingface_image import (
43
46
  HuggingFaceImageImport,
44
47
  HuggingFaceImageModel,
45
48
  )
49
+ from cortexgrid_infer.meshing import ServedMeshingModel
50
+ from cortexgrid_infer.meshing_serve import MeshingDeployment
51
+ from cortexgrid_infer.providers.huggingface_mesh import HuggingFaceMeshImport
46
52
 
47
53
  __all__ = [
48
54
  "CompletionChunk",
49
55
  "DeployedModel",
50
56
  "CompletingModel",
51
57
  "GeneratedImage",
58
+ "GeneratedMesh",
52
59
  "GeneratingModel",
60
+ "MeshingModel",
53
61
  "Message",
54
62
  "Tool",
55
63
  "ToolCall",
56
64
  "ToolSpec",
57
65
  "complete",
58
66
  "generate",
67
+ "mesh",
59
68
  "ModelDeployFailed",
60
69
  "detect_device",
61
70
  "split_model_id",
@@ -63,7 +72,10 @@ __all__ = [
63
72
  "HuggingFaceImport",
64
73
  "HuggingFaceCompletingImport",
65
74
  "HuggingFaceImageImport",
75
+ "HuggingFaceMeshImport",
66
76
  "AnthropicImport",
67
77
  "ServedCompletingModel",
68
78
  "HuggingFaceImageModel",
79
+ "ServedMeshingModel",
80
+ "MeshingDeployment",
69
81
  ]
@@ -8,6 +8,8 @@ from functools import partial
8
8
  from collections.abc import AsyncIterator
9
9
  from typing import Any, Callable, Sequence
10
10
 
11
+ import numpy as np
12
+
11
13
 
12
14
  @dataclass
13
15
  class ToolCall:
@@ -87,6 +89,26 @@ class GeneratingModel(DeployedModel):
87
89
  ) -> GeneratedImage: ...
88
90
 
89
91
 
92
+ @dataclass
93
+ class GeneratedMesh:
94
+ """One mesh: `vertices` (N, 3) float32, x to the right of the picture it was
95
+ made from, y up, z toward whoever looked at it; `faces` (M, 3) int32 indices
96
+ into them, counter-clockwise seen from outside; `colours` (N, 3) uint8, each
97
+ vertex's colour. Plus the resolved parameters the deployment used."""
98
+
99
+ vertices: np.ndarray
100
+ faces: np.ndarray
101
+ colours: np.ndarray
102
+ params: dict[str, Any] = field(default_factory=dict)
103
+
104
+
105
+ class MeshingModel(DeployedModel):
106
+ """A deployed model that makes a mesh of the object in one picture."""
107
+
108
+ @abc.abstractmethod
109
+ async def mesh(self, image: bytes, **kwargs: Any) -> GeneratedMesh: ...
110
+
111
+
90
112
  async def complete(
91
113
  deployed_model: CompletingModel,
92
114
  messages: list[Message],
@@ -115,3 +137,8 @@ async def generate(
115
137
  ) -> GeneratedImage:
116
138
  """Single image-generation request to the deployed model."""
117
139
  return await deployed_model.generate(prompt, image=image, **kwargs)
140
+
141
+
142
+ async def mesh(deployed_model: MeshingModel, image: bytes, **kwargs: Any) -> GeneratedMesh:
143
+ """Single image-to-mesh request to the deployed model."""
144
+ return await deployed_model.mesh(image, **kwargs)
@@ -0,0 +1,72 @@
1
+ """The wire protocol between a cortexgrid-served image-to-mesh app and its client.
2
+
3
+ An image-to-mesh serve app answers `POST /mesh`: the request carries a picture
4
+ of an object as a base64 PNG under `image`, plus whatever options the model
5
+ takes (a marching-cubes resolution, say) as further fields. The reply carries
6
+ the mesh as base64 little-endian arrays - `vertices` float32 x, y, z, `faces`
7
+ int32 indices, `colours` uint8 r, g, b per vertex - in the frame
8
+ `GeneratedMesh` documents, and `params`, what the model resolved.
9
+
10
+ Every such app speaks it through `cortexgrid_infer.meshing_serve.MeshingDeployment`,
11
+ so one client, `ServedMeshingModel`, serves every model of the family whoever
12
+ made it.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import base64
18
+ from dataclasses import dataclass
19
+ from typing import Any
20
+
21
+ import httpx
22
+ import numpy as np
23
+
24
+ from cortexgrid_infer.core import GeneratedMesh, MeshingModel
25
+
26
+ # How each array travels: its type, and how many values make one row.
27
+ VERTICES = ("<f4", 3)
28
+ FACES = ("<i4", 3)
29
+ COLOURS = ("u1", 3)
30
+
31
+
32
+ def encode(array: np.ndarray, kind: tuple[str, int]) -> str:
33
+ """`array` as the protocol sends it."""
34
+ dtype, _ = kind
35
+ return base64.b64encode(np.ascontiguousarray(array, dtype=dtype).tobytes()).decode("ascii")
36
+
37
+
38
+ def decode(data: str, kind: tuple[str, int]) -> np.ndarray:
39
+ """An array the protocol sent, one row per vertex or triangle."""
40
+ dtype, columns = kind
41
+ return np.frombuffer(base64.b64decode(data), dtype=dtype).reshape(-1, columns)
42
+
43
+
44
+ @dataclass
45
+ class ServedMeshingModel(MeshingModel):
46
+ """The client of any image-to-mesh serve app deployed at `url`."""
47
+
48
+ url: str
49
+ model_id: str
50
+
51
+ @property
52
+ def name(self) -> str:
53
+ return self.model_id
54
+
55
+ async def mesh(self, image: bytes, **options: Any) -> GeneratedMesh:
56
+ body: dict[str, Any] = {"image": base64.b64encode(image).decode("ascii"), **options}
57
+ # Meshing takes seconds; no client-side timeout.
58
+ async with httpx.AsyncClient(timeout=None) as client:
59
+ response = await client.post(f"{self.url}/mesh", json=body)
60
+ response.raise_for_status()
61
+ data = response.json()
62
+
63
+ return GeneratedMesh(
64
+ vertices=decode(data["vertices"], VERTICES),
65
+ faces=decode(data["faces"], FACES),
66
+ colours=decode(data["colours"], COLOURS),
67
+ params={
68
+ **data.get("params", {}),
69
+ "duration_s": data.get("duration_s"),
70
+ "model_id": self.model_id,
71
+ },
72
+ )
@@ -0,0 +1,72 @@
1
+ """The base of every image-to-mesh serve app: the `POST /mesh` route and loading the
2
+ model's files from the cortexgrid registry, around the two steps that differ per
3
+ model.
4
+
5
+ There is no one loader for image-to-mesh models the way `AutoModelForCausalLM`
6
+ or a diffusers `model_index.json` serves a whole family of repos: each model
7
+ ships its own code. So a model's serve app subclasses `MeshingDeployment` and
8
+ supplies `load` and `make_mesh`; the subclass inherits the route, whose protocol
9
+ is `cortexgrid_infer.meshing`'s. cortexgrid bundles the subclass's own file, so
10
+ the model's code travels with it.
11
+
12
+ class MyMeshDeployment(MeshingDeployment):
13
+ def load(self, path, device): ...
14
+ def make_mesh(self, image, **options) -> GeneratedMesh: ...
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import base64
20
+ import io
21
+ import time
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ import cortexgrid
26
+ from cortexgrid import serve
27
+ from fastapi import FastAPI
28
+ from PIL import Image, ImageOps
29
+ import torch
30
+
31
+ from cortexgrid_infer import meshing
32
+ from cortexgrid_infer.core import GeneratedMesh
33
+ from cortexgrid_infer.device import detect_device
34
+
35
+
36
+ _app = FastAPI()
37
+
38
+
39
+ @serve.ingress(_app)
40
+ class MeshingDeployment:
41
+ """One replica of an image-to-mesh model."""
42
+
43
+ def __init__(self, family: str, suffix: str, run_name: str) -> None:
44
+ self.device = detect_device()
45
+ self.load(Path(cortexgrid.load_model(family, suffix, run_name)), self.device)
46
+
47
+ def load(self, path: Path, device: torch.device) -> None:
48
+ """Build the model from its files at `path`, on `device`."""
49
+ raise NotImplementedError
50
+
51
+ def make_mesh(self, image: Image.Image, **options: Any) -> GeneratedMesh:
52
+ """The mesh of the object in `image`, in the frame `GeneratedMesh`
53
+ documents, with the options the request carried; `params` says what the
54
+ model resolved them to."""
55
+ raise NotImplementedError
56
+
57
+ @_app.post("/mesh")
58
+ async def mesh(self, body: dict[str, Any]) -> dict[str, Any]:
59
+ image = ImageOps.exif_transpose(Image.open(io.BytesIO(base64.b64decode(body["image"]))))
60
+ options = {key: value for key, value in body.items() if key != "image" and value is not None}
61
+
62
+ t0 = time.time()
63
+ made = self.make_mesh(image, **options)
64
+ duration = time.time() - t0
65
+
66
+ return {
67
+ "vertices": meshing.encode(made.vertices, meshing.VERTICES),
68
+ "faces": meshing.encode(made.faces, meshing.FACES),
69
+ "colours": meshing.encode(made.colours, meshing.COLOURS),
70
+ "params": made.params,
71
+ "duration_s": round(duration, 2),
72
+ }
@@ -0,0 +1,37 @@
1
+ """HuggingFace image-to-mesh provider: the importer an image-to-mesh model on the
2
+ Hub is taken into the cortexgrid registry with, and the client of what it serves.
3
+
4
+ Each such model brings its own code (see `cortexgrid_infer.meshing_serve`), so
5
+ this is a base: a model's own importer subclasses it and names its serve app.
6
+
7
+ class MyMeshImport(HuggingFaceMeshImport):
8
+ serve_app = MyMeshDeployment # a MeshingDeployment subclass
9
+ vram_gb = 6.0
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import dataclasses
15
+ from typing import ClassVar
16
+
17
+ import cortexgrid
18
+
19
+ from cortexgrid_infer.importing import HuggingFaceImport
20
+ from cortexgrid_infer.meshing import ServedMeshingModel
21
+
22
+
23
+ class HuggingFaceMeshImport(HuggingFaceImport):
24
+ """What `cortexgrid.import_model` needs to take an image-to-mesh model from
25
+ the Hub, less the serve app, which the subclass names."""
26
+
27
+ # What one replica needs at least, whatever the weights come to: meshing
28
+ # queries the model over a whole 3D grid, which takes memory the weights do
29
+ # not show.
30
+ vram_gb: ClassVar[float] = 0.0
31
+
32
+ def requirements(self) -> cortexgrid.ModelRequirements:
33
+ estimate = super().requirements()
34
+ return dataclasses.replace(estimate, vram_gb=max(estimate.vram_gb, self.vram_gb))
35
+
36
+ def client(self, url: str) -> ServedMeshingModel:
37
+ return ServedMeshingModel(url=url, model_id=self.hf_id)
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "cortexgrid-infer"
3
- version = "0.2.6"
4
- description = "Deploy and interact with models across providers (Anthropic, HuggingFace LLM + diffusion)"
3
+ version = "0.2.7"
4
+ description = "Deploy and interact with models across providers (Anthropic, HuggingFace LLM + diffusion + image-to-mesh)"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
7
7
  license-files = ["LICENSE"]
@@ -34,6 +34,8 @@ dependencies = [
34
34
  "accelerate>=0.33",
35
35
  "sentencepiece>=0.2",
36
36
  "protobuf>=4.0",
37
+ # Image-to-mesh: meshes travel as numpy arrays (core.GeneratedMesh).
38
+ "numpy>=1.24",
37
39
  ]
38
40
 
39
41
  [project.optional-dependencies]