cortexgrid-infer 0.3.7__tar.gz → 0.3.8__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 (32) hide show
  1. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/PKG-INFO +23 -24
  2. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/README.md +21 -22
  3. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/__init__.py +2 -4
  4. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/core.py +5 -10
  5. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/importers/base.py +0 -4
  6. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/protocols/completion.py +1 -10
  7. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/protocols/imaging.py +0 -10
  8. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/protocols/meshing.py +0 -10
  9. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/protocols/rewriting.py +0 -9
  10. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/registry.py +5 -13
  11. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/anthropic.py +2 -2
  12. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/base.py +5 -7
  13. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/gemini/text2image.py +3 -2
  14. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/gemini/text2text.py +3 -2
  15. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/image2mesh.py +2 -2
  16. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/text2image.py +2 -2
  17. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/text2text.py +2 -2
  18. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/text_rewriter.py +2 -2
  19. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/pyproject.toml +2 -2
  20. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/.gitignore +0 -0
  21. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/LICENSE +0 -0
  22. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/compiling.py +0 -0
  23. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/device.py +0 -0
  24. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/importers/__init__.py +0 -0
  25. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/importers/huggingface.py +0 -0
  26. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/protocols/__init__.py +0 -0
  27. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/py.typed +0 -0
  28. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/__init__.py +0 -0
  29. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/gemini/__init__.py +0 -0
  30. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/gemini/base.py +0 -0
  31. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/serve_apps/streamed_completion.py +0 -0
  32. {cortexgrid_infer-0.3.7 → cortexgrid_infer-0.3.8}/cortexgrid_infer/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cortexgrid-infer
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Serve apps per task (text-to-text, text-to-image, image-to-mesh) and importers per source (HuggingFace) for running models on cortexgrid
5
5
  Project-URL: Homepage, https://github.com/robodatalab/cortexgrid-infer
6
6
  Project-URL: Repository, https://github.com/robodatalab/cortexgrid-infer
@@ -9,7 +9,7 @@ License-File: LICENSE
9
9
  Requires-Python: <3.12,>=3.11
10
10
  Requires-Dist: accelerate>=0.33
11
11
  Requires-Dist: anthropic>=0.40.0
12
- Requires-Dist: cortexgrid>=0.3.12
12
+ Requires-Dist: cortexgrid>=0.3.16
13
13
  Requires-Dist: diffusers>=0.38
14
14
  Requires-Dist: fastapi>=0.133
15
15
  Requires-Dist: google-genai>=1.0
@@ -85,11 +85,12 @@ need:
85
85
  | `.serve_app` | the class cortexgrid bundles and runs on the cluster |
86
86
  | `.requirements()` | what one replica needs to be placed and to run |
87
87
  | `.config()` | settings the serve app reads at construction, if it needs any |
88
- | `.client(url)` | a client that speaks the deployed app's routes |
89
88
  | `.source` | *(importers only)* a callable that downloads the weights and returns the directory |
90
89
 
91
90
  One with weights goes through `cortexgrid.import_model`, one without through
92
- `cortexgrid.register_model`.
91
+ `cortexgrid.register_model`. Once deployed, the `cortexgrid.Deployment` hands back
92
+ the client that speaks the app's routes: `deployment.client()` once the app serves,
93
+ or `deployment.client_async()` at once, with `await model.is_ready()` to ask.
93
94
 
94
95
  ## Quick start
95
96
 
@@ -117,13 +118,11 @@ cortexgrid.remote(
117
118
  import_weights, imp, imp.requirements(), num_gpus=0, num_cpus=2
118
119
  ).result()
119
120
 
120
- # 2. Deploy — blocks until the Ray Serve app is running.
121
- deployment = cortexgrid.deploy_model(
122
- imp.family, imp.suffix, cortexgrid.IMPORTED, wait=True, timeout=1800
123
- )
121
+ # 2. Deploy.
122
+ deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED, timeout=1800)
124
123
 
125
- # 3. Inference.
126
- model = imp.client(deployment.url)
124
+ # 3. Inference — the client blocks until the Ray Serve app is running.
125
+ model = deployment.client()
127
126
 
128
127
  async def run():
129
128
  result = await mg.generate(model, "a red bicycle on a beach at sunrise")
@@ -281,8 +280,7 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
281
280
  `complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`):
282
281
 
283
282
  ```python
284
- imp = mg.HuggingFaceImporter("Qwen/Qwen2.5-7B-Instruct", mg.Text2Text)
285
- model = imp.client(deployment.url)
283
+ model = deployment.client()
286
284
  messages = [{"role": "user", "content": "Explain RAG in one sentence."}]
287
285
 
288
286
  async for chunk in mg.complete(model, messages, max_new_tokens=512, temperature=0.7):
@@ -323,7 +321,7 @@ a re-deploy rather than a re-registration.
323
321
 
324
322
  A replica asks for no hardware at all, so it is placed on any node, CPU-only
325
323
  included. From there it deploys and streams exactly like a cluster-served model:
326
- `entry.client(deployment.url)` returns the same `ServedCompletingModel`. Anthropic
324
+ `deployment.client()` returns the same `ServedCompletingModel`. Anthropic
327
325
  reports tool calls as structured blocks rather than as generated text, so the
328
326
  serve app re-encodes them into the text form the client parses.
329
327
 
@@ -346,7 +344,7 @@ cortexgrid.register_model(
346
344
 
347
345
  Everything said above for Anthropic holds: `config` carries the model name and
348
346
  the name of the secret (`api_key_secret="..."` to use another), both editable on
349
- the model card; a replica needs no hardware; and `entry.client(deployment.url)`
347
+ the model card; a replica needs no hardware; and `deployment.client()`
350
348
  is a `ServedCompletingModel`, with Gemini's function calls re-encoded into the
351
349
  text form the client parses.
352
350
 
@@ -356,7 +354,8 @@ client is a `ServedGeneratingModel`:
356
354
  ```python
357
355
  entry = mg.Hosted("gemini-2.5-flash-image", mg.GeminiText2Image)
358
356
  ...
359
- picture = await mg.generate(entry.client(deployment.url), "a fox in the snow", size=2048)
357
+ model = deployment.client()
358
+ picture = await mg.generate(model, "a fox in the snow", size=2048)
360
359
  ```
361
360
 
362
361
  It takes a prompt only, and makes a square picture. `size` picks Gemini's size
@@ -368,28 +367,28 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
368
367
  | | |
369
368
  |---|---|
370
369
  | `HuggingFaceImporter(hf_id, serve_app, token=None, ignore_patterns=None)` | Importer for a model on the HuggingFace Hub, run by `serve_app`. |
371
- | `Importer` | Base of the importers: identity, scratch directory, `source`, and `requirements()` / `client(url)` taken from the serve app. A new source implements `download(local_dir)` and `weights()`. |
370
+ | `Importer` | Base of the importers: identity, scratch directory, `source`, and `requirements()` taken from the serve app. A new source implements `download(local_dir)` and `weights()`. |
372
371
  | `Hosted(model_id, serve_app, **settings)` | Entry for a model hosted elsewhere; no weights. `settings` are the serve app's `config` arguments. |
373
- | `ModelEntry` | Base of both: `model_id`, `family`, `suffix`, `serve_app`, `requirements()`, `config()`, `client(url)`. |
372
+ | `ModelEntry` | Base of both: `model_id`, `family`, `suffix`, `serve_app`, `requirements()`, `config()`. |
374
373
  | `Text2Text`, `Text2Image`, `TextRewriter`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
375
374
  | `AnthropicText2Text` | Serve app that forwards to the Anthropic API (a `HostedModel`). |
376
375
  | `GeminiText2Text`, `GeminiText2Image` | Serve apps that forward to the Gemini API (`HostedModel`s sharing the `GeminiModel` base). |
377
- | `LocalModel` | Base of the serve apps that run weights: `ignore_patterns()`, `bytes_per_param`, `min_vram_gb`, `requirements(weights)`, `config()` (the model card's defaults, `compile: "false"` among them), `client(url, name)`. |
378
- | `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`, `client(url, name)`. |
376
+ | `LocalModel` | Base of the serve apps that run weights: `ignore_patterns()`, `bytes_per_param`, `min_vram_gb`, `requirements(weights)`, `config()` (the model card's defaults, `compile: "false"` among them), `client(deployment)`. |
377
+ | `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`, `client(deployment)`. |
379
378
  | `Weights(params=None, file_bytes=0)` | What an importer reads about the weights, handed to the serve app to size a replica. |
380
379
  | `complete(model, messages, tools=None, max_new_tokens=2048, temperature=0.7, **kw)` | Async stream of `CompletionChunk` for a `CompletingModel`. |
381
380
  | `generate(model, prompt, *, image=None, **kw) -> GeneratedImage` | One image from a `GeneratingModel`. |
382
- | `ServedCompletingModel(url, model_id)` | Client for any deployed completion app. |
383
- | `ServedGeneratingModel(url, model_id)` | Client for any deployed text-to-image app. |
384
- | `ServedMeshingModel(url, model_id)` | Client for any deployed image-to-mesh app. |
381
+ | `ServedCompletingModel(key, url)` | Client for any deployed completion app. |
382
+ | `ServedGeneratingModel(key, url)` | Client for any deployed text-to-image app. |
383
+ | `ServedMeshingModel(key, url)` | Client for any deployed image-to-mesh app. |
385
384
  | `split_model_id(model_id) -> (family, suffix)` | The registry identity an importer derives. |
386
385
  | `detect_device()` | The torch device a serve app should load onto. |
387
386
  | `compiling.Mode.GRAPHED` / `compiling.Mode.FUSED` | The two modes a serve app chooses between. See [Compilation](#compilation). |
388
387
  | `compiling.supported(device)` / `compile_module(module, device, mode)` / `compile_pipeline(pipe, device, mode)` | Whether to compile here, and what a serve app calls to do it. |
389
388
  | `ModelDeployFailed` | Re-exported from cortexgrid; subclasses `RuntimeError`. |
390
389
 
391
- Types: `DeployedModel`, `CompletingModel`, `GeneratingModel`, `MeshingModel`,
392
- `CompletionChunk`, `GeneratedImage`, `GeneratedMesh`, `ToolCall`, `Message`, `Tool`,
390
+ Types: `CompletingModel`, `GeneratingModel`, `MeshingModel`, `RewritingModel` (each
391
+ a `cortexgrid.DeploymentClient`), `CompletionChunk`, `GeneratedImage`, `GeneratedMesh`, `ToolCall`, `Message`, `Tool`,
393
392
  `ToolSpec`.
394
393
 
395
394
  Extending it:
@@ -57,11 +57,12 @@ need:
57
57
  | `.serve_app` | the class cortexgrid bundles and runs on the cluster |
58
58
  | `.requirements()` | what one replica needs to be placed and to run |
59
59
  | `.config()` | settings the serve app reads at construction, if it needs any |
60
- | `.client(url)` | a client that speaks the deployed app's routes |
61
60
  | `.source` | *(importers only)* a callable that downloads the weights and returns the directory |
62
61
 
63
62
  One with weights goes through `cortexgrid.import_model`, one without through
64
- `cortexgrid.register_model`.
63
+ `cortexgrid.register_model`. Once deployed, the `cortexgrid.Deployment` hands back
64
+ the client that speaks the app's routes: `deployment.client()` once the app serves,
65
+ or `deployment.client_async()` at once, with `await model.is_ready()` to ask.
65
66
 
66
67
  ## Quick start
67
68
 
@@ -89,13 +90,11 @@ cortexgrid.remote(
89
90
  import_weights, imp, imp.requirements(), num_gpus=0, num_cpus=2
90
91
  ).result()
91
92
 
92
- # 2. Deploy — blocks until the Ray Serve app is running.
93
- deployment = cortexgrid.deploy_model(
94
- imp.family, imp.suffix, cortexgrid.IMPORTED, wait=True, timeout=1800
95
- )
93
+ # 2. Deploy.
94
+ deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED, timeout=1800)
96
95
 
97
- # 3. Inference.
98
- model = imp.client(deployment.url)
96
+ # 3. Inference — the client blocks until the Ray Serve app is running.
97
+ model = deployment.client()
99
98
 
100
99
  async def run():
101
100
  result = await mg.generate(model, "a red bicycle on a beach at sunrise")
@@ -253,8 +252,7 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
253
252
  `complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`):
254
253
 
255
254
  ```python
256
- imp = mg.HuggingFaceImporter("Qwen/Qwen2.5-7B-Instruct", mg.Text2Text)
257
- model = imp.client(deployment.url)
255
+ model = deployment.client()
258
256
  messages = [{"role": "user", "content": "Explain RAG in one sentence."}]
259
257
 
260
258
  async for chunk in mg.complete(model, messages, max_new_tokens=512, temperature=0.7):
@@ -295,7 +293,7 @@ a re-deploy rather than a re-registration.
295
293
 
296
294
  A replica asks for no hardware at all, so it is placed on any node, CPU-only
297
295
  included. From there it deploys and streams exactly like a cluster-served model:
298
- `entry.client(deployment.url)` returns the same `ServedCompletingModel`. Anthropic
296
+ `deployment.client()` returns the same `ServedCompletingModel`. Anthropic
299
297
  reports tool calls as structured blocks rather than as generated text, so the
300
298
  serve app re-encodes them into the text form the client parses.
301
299
 
@@ -318,7 +316,7 @@ cortexgrid.register_model(
318
316
 
319
317
  Everything said above for Anthropic holds: `config` carries the model name and
320
318
  the name of the secret (`api_key_secret="..."` to use another), both editable on
321
- the model card; a replica needs no hardware; and `entry.client(deployment.url)`
319
+ the model card; a replica needs no hardware; and `deployment.client()`
322
320
  is a `ServedCompletingModel`, with Gemini's function calls re-encoded into the
323
321
  text form the client parses.
324
322
 
@@ -328,7 +326,8 @@ client is a `ServedGeneratingModel`:
328
326
  ```python
329
327
  entry = mg.Hosted("gemini-2.5-flash-image", mg.GeminiText2Image)
330
328
  ...
331
- picture = await mg.generate(entry.client(deployment.url), "a fox in the snow", size=2048)
329
+ model = deployment.client()
330
+ picture = await mg.generate(model, "a fox in the snow", size=2048)
332
331
  ```
333
332
 
334
333
  It takes a prompt only, and makes a square picture. `size` picks Gemini's size
@@ -340,28 +339,28 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
340
339
  | | |
341
340
  |---|---|
342
341
  | `HuggingFaceImporter(hf_id, serve_app, token=None, ignore_patterns=None)` | Importer for a model on the HuggingFace Hub, run by `serve_app`. |
343
- | `Importer` | Base of the importers: identity, scratch directory, `source`, and `requirements()` / `client(url)` taken from the serve app. A new source implements `download(local_dir)` and `weights()`. |
342
+ | `Importer` | Base of the importers: identity, scratch directory, `source`, and `requirements()` taken from the serve app. A new source implements `download(local_dir)` and `weights()`. |
344
343
  | `Hosted(model_id, serve_app, **settings)` | Entry for a model hosted elsewhere; no weights. `settings` are the serve app's `config` arguments. |
345
- | `ModelEntry` | Base of both: `model_id`, `family`, `suffix`, `serve_app`, `requirements()`, `config()`, `client(url)`. |
344
+ | `ModelEntry` | Base of both: `model_id`, `family`, `suffix`, `serve_app`, `requirements()`, `config()`. |
346
345
  | `Text2Text`, `Text2Image`, `TextRewriter`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
347
346
  | `AnthropicText2Text` | Serve app that forwards to the Anthropic API (a `HostedModel`). |
348
347
  | `GeminiText2Text`, `GeminiText2Image` | Serve apps that forward to the Gemini API (`HostedModel`s sharing the `GeminiModel` base). |
349
- | `LocalModel` | Base of the serve apps that run weights: `ignore_patterns()`, `bytes_per_param`, `min_vram_gb`, `requirements(weights)`, `config()` (the model card's defaults, `compile: "false"` among them), `client(url, name)`. |
350
- | `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`, `client(url, name)`. |
348
+ | `LocalModel` | Base of the serve apps that run weights: `ignore_patterns()`, `bytes_per_param`, `min_vram_gb`, `requirements(weights)`, `config()` (the model card's defaults, `compile: "false"` among them), `client(deployment)`. |
349
+ | `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`, `client(deployment)`. |
351
350
  | `Weights(params=None, file_bytes=0)` | What an importer reads about the weights, handed to the serve app to size a replica. |
352
351
  | `complete(model, messages, tools=None, max_new_tokens=2048, temperature=0.7, **kw)` | Async stream of `CompletionChunk` for a `CompletingModel`. |
353
352
  | `generate(model, prompt, *, image=None, **kw) -> GeneratedImage` | One image from a `GeneratingModel`. |
354
- | `ServedCompletingModel(url, model_id)` | Client for any deployed completion app. |
355
- | `ServedGeneratingModel(url, model_id)` | Client for any deployed text-to-image app. |
356
- | `ServedMeshingModel(url, model_id)` | Client for any deployed image-to-mesh app. |
353
+ | `ServedCompletingModel(key, url)` | Client for any deployed completion app. |
354
+ | `ServedGeneratingModel(key, url)` | Client for any deployed text-to-image app. |
355
+ | `ServedMeshingModel(key, url)` | Client for any deployed image-to-mesh app. |
357
356
  | `split_model_id(model_id) -> (family, suffix)` | The registry identity an importer derives. |
358
357
  | `detect_device()` | The torch device a serve app should load onto. |
359
358
  | `compiling.Mode.GRAPHED` / `compiling.Mode.FUSED` | The two modes a serve app chooses between. See [Compilation](#compilation). |
360
359
  | `compiling.supported(device)` / `compile_module(module, device, mode)` / `compile_pipeline(pipe, device, mode)` | Whether to compile here, and what a serve app calls to do it. |
361
360
  | `ModelDeployFailed` | Re-exported from cortexgrid; subclasses `RuntimeError`. |
362
361
 
363
- Types: `DeployedModel`, `CompletingModel`, `GeneratingModel`, `MeshingModel`,
364
- `CompletionChunk`, `GeneratedImage`, `GeneratedMesh`, `ToolCall`, `Message`, `Tool`,
362
+ Types: `CompletingModel`, `GeneratingModel`, `MeshingModel`, `RewritingModel` (each
363
+ a `cortexgrid.DeploymentClient`), `CompletionChunk`, `GeneratedImage`, `GeneratedMesh`, `ToolCall`, `Message`, `Tool`,
365
364
  `ToolSpec`.
366
365
 
367
366
  Extending it:
@@ -18,8 +18,8 @@ nothing else, split along the two things a model is:
18
18
  family=imp.family, suffix=imp.suffix,
19
19
  requirements=imp.requirements(),
20
20
  )
21
- deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED, wait=True)
22
- model = imp.client(deployment.url)
21
+ deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED)
22
+ model = deployment.client()
23
23
 
24
24
  A model hosted elsewhere has no weights and so no importer: its serve app
25
25
  forwards to it, and a `Hosted` entry registers it.
@@ -31,7 +31,6 @@ from cortexgrid import ModelDeployFailed
31
31
 
32
32
  from cortexgrid_infer.core import (
33
33
  CompletionChunk,
34
- DeployedModel,
35
34
  CompletingModel,
36
35
  GeneratedImage,
37
36
  GeneratedMesh,
@@ -71,7 +70,6 @@ from cortexgrid_infer.importers import HuggingFaceImporter, Importer
71
70
 
72
71
  __all__ = [
73
72
  "CompletionChunk",
74
- "DeployedModel",
75
73
  "CompletingModel",
76
74
  "GeneratedImage",
77
75
  "GeneratedMesh",
@@ -8,6 +8,7 @@ from functools import partial
8
8
  from collections.abc import AsyncIterator
9
9
  from typing import Any, Callable, Sequence
10
10
 
11
+ import cortexgrid
11
12
  import numpy as np
12
13
 
13
14
 
@@ -64,13 +65,7 @@ ToolSpec = dict[str, Any]
64
65
  Tool = Callable[..., Any] | ToolSpec
65
66
 
66
67
 
67
- class DeployedModel(abc.ABC):
68
- @property
69
- @abc.abstractmethod
70
- def name(self) -> str: ...
71
-
72
-
73
- class CompletingModel(DeployedModel):
68
+ class CompletingModel(cortexgrid.DeploymentClient, abc.ABC):
74
69
  @abc.abstractmethod
75
70
  def complete(
76
71
  self,
@@ -93,7 +88,7 @@ class GeneratedImage:
93
88
  params: dict[str, Any] = field(default_factory=dict)
94
89
 
95
90
 
96
- class GeneratingModel(DeployedModel):
91
+ class GeneratingModel(cortexgrid.DeploymentClient, abc.ABC):
97
92
  """A deployed image model. Unlike completion, generation is a single
98
93
  request/response (no token streaming), so `generate` returns one result."""
99
94
 
@@ -124,14 +119,14 @@ class GeneratedMesh:
124
119
  params: dict[str, Any] = field(default_factory=dict)
125
120
 
126
121
 
127
- class MeshingModel(DeployedModel):
122
+ class MeshingModel(cortexgrid.DeploymentClient, abc.ABC):
128
123
  """A deployed model that makes a mesh of the object in one picture."""
129
124
 
130
125
  @abc.abstractmethod
131
126
  async def mesh(self, image: bytes, **kwargs: Any) -> GeneratedMesh: ...
132
127
 
133
128
 
134
- class RewritingModel(DeployedModel):
129
+ class RewritingModel(cortexgrid.DeploymentClient, abc.ABC):
135
130
  """A deployed model that rewrites one text into another - corrects,
136
131
  paraphrases, summarises or translates it. Like generation, a single
137
132
  request/response."""
@@ -29,7 +29,6 @@ from typing import Self
29
29
 
30
30
  import cortexgrid
31
31
 
32
- from cortexgrid_infer.core import DeployedModel
33
32
  from cortexgrid_infer.registry import ModelEntry, split_model_id
34
33
  from cortexgrid_infer.serve_apps.base import LocalModel, Weights
35
34
 
@@ -97,6 +96,3 @@ class Importer(ModelEntry):
97
96
 
98
97
  def config(self) -> dict[str, str]:
99
98
  return self.serve_app.config()
100
-
101
- def client(self, url: str) -> DeployedModel:
102
- return self.serve_app.client(url, self.model_id)
@@ -13,7 +13,6 @@ back as `CompletionChunk`s and reassembles the tool calls.
13
13
  from __future__ import annotations
14
14
 
15
15
  from collections.abc import AsyncIterator
16
- from dataclasses import dataclass
17
16
  import json
18
17
  from typing import Any, Sequence
19
18
 
@@ -22,19 +21,11 @@ import httpx
22
21
  from cortexgrid_infer.core import CompletingModel, CompletionChunk, Message, Tool
23
22
  from cortexgrid_infer.utils import build_tool_map, normalize_tools
24
23
 
25
- @dataclass
26
24
  class ServedCompletingModel(CompletingModel):
27
25
  """Client for a completion app cortexgrid has deployed at `url`.
28
26
 
29
27
  Provider-agnostic: every completion serve app speaks the protocol above, so
30
- which model is behind it only shows up in `model_id`."""
31
-
32
- url: str
33
- model_id: str
34
-
35
- @property
36
- def name(self) -> str:
37
- return self.model_id
28
+ which model is behind it only shows up in its deployment's `key`."""
38
29
 
39
30
  async def loglikelihoods(
40
31
  self, messages: list[Message], continuations: list[str]
@@ -15,7 +15,6 @@ task whoever made it.
15
15
  from __future__ import annotations
16
16
 
17
17
  import base64
18
- from dataclasses import dataclass
19
18
  from typing import Any
20
19
 
21
20
  import httpx
@@ -23,17 +22,9 @@ import httpx
23
22
  from cortexgrid_infer.core import GeneratedImage, GeneratingModel
24
23
 
25
24
 
26
- @dataclass
27
25
  class ServedGeneratingModel(GeneratingModel):
28
26
  """The client of any text-to-image serve app deployed at `url`."""
29
27
 
30
- url: str
31
- model_id: str
32
-
33
- @property
34
- def name(self) -> str:
35
- return self.model_id
36
-
37
28
  async def generate(
38
29
  self,
39
30
  prompt: str,
@@ -69,6 +60,5 @@ class ServedGeneratingModel(GeneratingModel):
69
60
  "guidance": data.get("guidance", guidance),
70
61
  "seed": data.get("seed", seed),
71
62
  "duration_s": data.get("duration_s"),
72
- "model_id": self.model_id,
73
63
  },
74
64
  )
@@ -15,7 +15,6 @@ Every such app speaks it through
15
15
  from __future__ import annotations
16
16
 
17
17
  import base64
18
- from dataclasses import dataclass
19
18
  from typing import Any
20
19
 
21
20
  import httpx
@@ -41,17 +40,9 @@ def decode(data: str, kind: tuple[str, int]) -> np.ndarray:
41
40
  return np.frombuffer(base64.b64decode(data), dtype=dtype).reshape(-1, columns)
42
41
 
43
42
 
44
- @dataclass
45
43
  class ServedMeshingModel(MeshingModel):
46
44
  """The client of any image-to-mesh serve app deployed at `url`."""
47
45
 
48
- url: str
49
- model_id: str
50
-
51
- @property
52
- def name(self) -> str:
53
- return self.model_id
54
-
55
46
  async def mesh(self, image: bytes, **options: Any) -> GeneratedMesh:
56
47
  body: dict[str, Any] = {"image": base64.b64encode(image).decode("ascii"), **options}
57
48
  # Meshing takes seconds; no client-side timeout.
@@ -67,6 +58,5 @@ class ServedMeshingModel(MeshingModel):
67
58
  params={
68
59
  **data.get("params", {}),
69
60
  "duration_s": data.get("duration_s"),
70
- "model_id": self.model_id,
71
61
  },
72
62
  )
@@ -15,7 +15,6 @@ every model of the task whoever made it.
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from dataclasses import dataclass
19
18
  from typing import Any
20
19
 
21
20
  import httpx
@@ -23,17 +22,9 @@ import httpx
23
22
  from cortexgrid_infer.core import RewritingModel
24
23
 
25
24
 
26
- @dataclass
27
25
  class ServedRewritingModel(RewritingModel):
28
26
  """The client of any rewriting serve app deployed at `url`."""
29
27
 
30
- url: str
31
- model_id: str
32
-
33
- @property
34
- def name(self) -> str:
35
- return self.model_id
36
-
37
28
  async def rewrite(
38
29
  self, text: str, max_new_tokens: int = 512, **generation_options: Any
39
30
  ) -> str:
@@ -5,9 +5,9 @@ cortexgrid stores whatever weights it is handed under whatever key it is given,
5
5
  and serves them with whatever class it was told to bundle. It deliberately knows
6
6
  nothing about where a model came from or what shape it is. A `ModelEntry`
7
7
  supplies exactly that missing knowledge - the registry identity, the serve app,
8
- the hardware it needs, its config, and the client - and drives no lifecycle of
9
- its own: importing, deploying and deleting stay the caller's calls against the
10
- cortexgrid SDK.
8
+ the hardware it needs, and its config - and drives no lifecycle of its own:
9
+ importing, deploying and deleting stay the caller's calls against the cortexgrid
10
+ SDK, and a deployment's client is the deployment's `client()`.
11
11
 
12
12
  A model with weights is an importer's (`cortexgrid_infer.importers`) and goes
13
13
  through `import_model`. A model hosted elsewhere is a `Hosted` entry and goes
@@ -27,7 +27,6 @@ import abc
27
27
 
28
28
  import cortexgrid
29
29
 
30
- from cortexgrid_infer.core import DeployedModel
31
30
  from cortexgrid_infer.serve_apps.base import HostedModel
32
31
 
33
32
 
@@ -49,8 +48,8 @@ def split_model_id(model_id: str) -> tuple[str, str]:
49
48
  class ModelEntry(abc.ABC):
50
49
  """One model as the registry files it and cortexgrid serves it."""
51
50
 
52
- # The id the model is known by where it came from; the registry key and the
53
- # client's name derive from it.
51
+ # The id the model is known by where it came from; the registry key derives
52
+ # from it.
54
53
  model_id: str
55
54
 
56
55
  # The class cortexgrid bundles and instantiates on the cluster.
@@ -72,10 +71,6 @@ class ModelEntry(abc.ABC):
72
71
  bundled code already knows everything it needs."""
73
72
  return {}
74
73
 
75
- @abc.abstractmethod
76
- def client(self, url: str) -> DeployedModel:
77
- """Return a client for this model's serve app, deployed at `url`."""
78
-
79
74
 
80
75
  class Hosted(ModelEntry):
81
76
  """A model hosted elsewhere, served by a `HostedModel` app that forwards to it.
@@ -97,6 +92,3 @@ class Hosted(ModelEntry):
97
92
 
98
93
  def config(self) -> dict[str, str]:
99
94
  return dict(self._config)
100
-
101
- def client(self, url: str) -> DeployedModel:
102
- return self.serve_app.client(url, self.model_id)
@@ -129,8 +129,8 @@ class AnthropicText2Text(HostedModel):
129
129
  return {MODEL_PARAM: model_id, API_KEY_SECRET_PARAM: api_key_secret}
130
130
 
131
131
  @classmethod
132
- def client(cls, url: str, name: str) -> ServedCompletingModel:
133
- return ServedCompletingModel(url=url, model_id=name)
132
+ def client(cls, deployment: cortexgrid.Deployment[ServedCompletingModel]) -> ServedCompletingModel:
133
+ return ServedCompletingModel(key=deployment.key, url=deployment.url)
134
134
 
135
135
  def __init__(self, deployment: cortexgrid.DeploymentKey) -> None:
136
136
  config = cortexgrid.model_config(deployment)
@@ -20,12 +20,10 @@ in step with it.
20
20
  from __future__ import annotations
21
21
 
22
22
  from dataclasses import dataclass
23
- from typing import ClassVar
23
+ from typing import Any, ClassVar
24
24
 
25
25
  import cortexgrid
26
26
 
27
- from cortexgrid_infer.core import DeployedModel
28
-
29
27
  _GIB = float(1 << 30)
30
28
 
31
29
  ENABLE_THINKING_PARAM = "enable_thinking"
@@ -107,8 +105,8 @@ class LocalModel:
107
105
  return {COMPILE_PARAM: "false"}
108
106
 
109
107
  @classmethod
110
- def client(cls, url: str, name: str) -> DeployedModel:
111
- """A client for this app deployed at `url`, serving the model `name`."""
108
+ def client(cls, deployment: cortexgrid.Deployment[Any]) -> cortexgrid.DeploymentClient:
109
+ """A client for this app's `deployment`."""
112
110
  raise NotImplementedError
113
111
 
114
112
 
@@ -128,6 +126,6 @@ class HostedModel:
128
126
  return cortexgrid.ModelRequirements()
129
127
 
130
128
  @classmethod
131
- def client(cls, url: str, name: str) -> DeployedModel:
132
- """A client for this app deployed at `url`, serving the model `name`."""
129
+ def client(cls, deployment: cortexgrid.Deployment[Any]) -> cortexgrid.DeploymentClient:
130
+ """A client for this app's `deployment`."""
133
131
  raise NotImplementedError
@@ -13,6 +13,7 @@ import io
13
13
  import time
14
14
  from typing import Any
15
15
 
16
+ import cortexgrid
16
17
  from cortexgrid import serve
17
18
  from fastapi import FastAPI, HTTPException
18
19
  from google.genai import types
@@ -63,8 +64,8 @@ def no_image_reason(response: types.GenerateContentResponse) -> str:
63
64
  @serve.ingress(_app)
64
65
  class GeminiText2Image(GeminiModel):
65
66
  @classmethod
66
- def client(cls, url: str, name: str) -> ServedGeneratingModel:
67
- return ServedGeneratingModel(url=url, model_id=name)
67
+ def client(cls, deployment: cortexgrid.Deployment[ServedGeneratingModel]) -> ServedGeneratingModel:
68
+ return ServedGeneratingModel(key=deployment.key, url=deployment.url)
68
69
 
69
70
  @_app.post("/generate")
70
71
  async def generate(self, body: dict[str, Any]) -> dict[str, Any]:
@@ -11,6 +11,7 @@ import base64
11
11
  from collections.abc import AsyncIterator
12
12
  from typing import Any
13
13
 
14
+ import cortexgrid
14
15
  from cortexgrid import serve
15
16
  from fastapi import FastAPI, HTTPException, status
16
17
  from fastapi.responses import StreamingResponse
@@ -124,8 +125,8 @@ class GeminiText2Text(GeminiModel):
124
125
  }
125
126
 
126
127
  @classmethod
127
- def client(cls, url: str, name: str) -> ServedCompletingModel:
128
- return ServedCompletingModel(url=url, model_id=name)
128
+ def client(cls, deployment: cortexgrid.Deployment[ServedCompletingModel]) -> ServedCompletingModel:
129
+ return ServedCompletingModel(key=deployment.key, url=deployment.url)
129
130
 
130
131
  @_app.post("/complete")
131
132
  async def complete(self, body: dict[str, Any]) -> StreamingResponse:
@@ -51,8 +51,8 @@ class Image2Mesh(LocalModel):
51
51
  show - querying the model over a whole 3D grid usually does."""
52
52
 
53
53
  @classmethod
54
- def client(cls, url: str, name: str) -> ServedMeshingModel:
55
- return ServedMeshingModel(url=url, model_id=name)
54
+ def client(cls, deployment: cortexgrid.Deployment[ServedMeshingModel]) -> ServedMeshingModel:
55
+ return ServedMeshingModel(key=deployment.key, url=deployment.url)
56
56
 
57
57
  def __init__(self, deployment: cortexgrid.DeploymentKey) -> None:
58
58
  self.device = detect_device()
@@ -81,8 +81,8 @@ class Text2Image(LocalModel):
81
81
  return _DEFAULT_IGNORE + [p.strip() for p in extra.split(",") if p.strip()]
82
82
 
83
83
  @classmethod
84
- def client(cls, url: str, name: str) -> ServedGeneratingModel:
85
- return ServedGeneratingModel(url=url, model_id=name)
84
+ def client(cls, deployment: cortexgrid.Deployment[ServedGeneratingModel]) -> ServedGeneratingModel:
85
+ return ServedGeneratingModel(key=deployment.key, url=deployment.url)
86
86
 
87
87
  def __init__(self, deployment: cortexgrid.DeploymentKey) -> None:
88
88
  path = cortexgrid.load_model(deployment.family, deployment.suffix, deployment.run_name)
@@ -123,8 +123,8 @@ class Text2Text(LocalModel):
123
123
  return {**super().config(), ENABLE_THINKING_PARAM: "true"}
124
124
 
125
125
  @classmethod
126
- def client(cls, url: str, name: str) -> ServedCompletingModel:
127
- return ServedCompletingModel(url=url, model_id=name)
126
+ def client(cls, deployment: cortexgrid.Deployment[ServedCompletingModel]) -> ServedCompletingModel:
127
+ return ServedCompletingModel(key=deployment.key, url=deployment.url)
128
128
 
129
129
  def __init__(self, deployment: cortexgrid.DeploymentKey) -> None:
130
130
  path = cortexgrid.load_model(
@@ -47,8 +47,8 @@ def _bucket(length: int) -> int:
47
47
  @serve.ingress(_app)
48
48
  class TextRewriter(LocalModel):
49
49
  @classmethod
50
- def client(cls, url: str, name: str) -> ServedRewritingModel:
51
- return ServedRewritingModel(url=url, model_id=name)
50
+ def client(cls, deployment: cortexgrid.Deployment[ServedRewritingModel]) -> ServedRewritingModel:
51
+ return ServedRewritingModel(key=deployment.key, url=deployment.url)
52
52
 
53
53
  def __init__(self, deployment: cortexgrid.DeploymentKey) -> None:
54
54
  path = cortexgrid.load_model(deployment.family, deployment.suffix, deployment.run_name)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cortexgrid-infer"
3
- version = "0.3.7"
3
+ version = "0.3.8"
4
4
  description = "Serve apps per task (text-to-text, text-to-image, image-to-mesh) and importers per source (HuggingFace) for running models on cortexgrid"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -17,7 +17,7 @@ dependencies = [
17
17
  # redundant PUT - which is why this library has no reuse guard of its own.
18
18
  # 0.3.12: DeploymentKey - a serve-app is constructed with its deployment's
19
19
  # key and reads that deployment's config with model_config(key).
20
- "cortexgrid>=0.3.12",
20
+ "cortexgrid>=0.3.16",
21
21
  "torch>=2.0.0",
22
22
  # 4.47: CompileConfig, which the completion serve app hands to `generate`
23
23
  # alongside a static cache so transformers compiles the decode step itself.