cortexgrid-infer 0.3.8__tar.gz → 0.3.9__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.
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/PKG-INFO +40 -39
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/README.md +37 -37
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/__init__.py +6 -14
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/core.py +72 -55
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/anthropic.py +41 -53
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/base.py +3 -13
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/gemini/text2image.py +32 -29
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/gemini/text2text.py +45 -58
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/image2mesh.py +25 -29
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/streamed_completion.py +27 -40
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/text2image.py +35 -36
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/text2text.py +47 -73
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/text_rewriter.py +19 -20
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/utils.py +13 -24
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/pyproject.toml +3 -2
- cortexgrid_infer-0.3.8/cortexgrid_infer/protocols/__init__.py +0 -14
- cortexgrid_infer-0.3.8/cortexgrid_infer/protocols/completion.py +0 -83
- cortexgrid_infer-0.3.8/cortexgrid_infer/protocols/imaging.py +0 -64
- cortexgrid_infer-0.3.8/cortexgrid_infer/protocols/meshing.py +0 -62
- cortexgrid_infer-0.3.8/cortexgrid_infer/protocols/rewriting.py +0 -35
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/.gitignore +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/LICENSE +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/compiling.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/device.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/importers/__init__.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/importers/base.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/importers/huggingface.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/py.typed +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/registry.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/__init__.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/gemini/__init__.py +0 -0
- {cortexgrid_infer-0.3.8 → cortexgrid_infer-0.3.9}/cortexgrid_infer/serve_apps/gemini/base.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cortexgrid-infer
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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
|
+
Requires-Dist: cortexgrid>=0.3.18
|
|
13
13
|
Requires-Dist: diffusers>=0.38
|
|
14
14
|
Requires-Dist: fastapi>=0.133
|
|
15
15
|
Requires-Dist: google-genai>=1.0
|
|
@@ -23,6 +23,7 @@ Requires-Dist: sentencepiece>=0.2
|
|
|
23
23
|
Requires-Dist: torch>=2.0.0
|
|
24
24
|
Requires-Dist: transformers>=4.47.0
|
|
25
25
|
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: parameterized>=0.9.0; extra == 'dev'
|
|
26
27
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
@@ -50,23 +51,24 @@ Upgrading from 0.2.x? See [MIGRATION.md](https://github.com/robodatalab/cortexgr
|
|
|
50
51
|
A model is two things, and the library keeps them apart:
|
|
51
52
|
|
|
52
53
|
- **What runs it** — a serve app in `cortexgrid_infer.serve_apps`, named for the task
|
|
53
|
-
it runs, not for where the model came from. It loads the weights
|
|
54
|
-
|
|
55
|
-
them. One serve app runs every model of its task,
|
|
54
|
+
it runs, not for where the model came from. It loads the weights and serves the
|
|
55
|
+
task's endpoints, declared by the task's type (`CompletingModel`, say); cortexgrid
|
|
56
|
+
generates the client that calls them. One serve app runs every model of its task,
|
|
57
|
+
whichever source staged it.
|
|
56
58
|
- **Where its weights come from** — an importer in `cortexgrid_infer.importers`, one
|
|
57
59
|
per source. It names the model in the registry, downloads the weights, and sizes
|
|
58
60
|
them from the source's metadata. It is handed the serve app, so one importer takes
|
|
59
61
|
any model its source holds.
|
|
60
62
|
|
|
61
|
-
| Serve app | Task | Runs |
|
|
63
|
+
| Serve app | Task | Runs | Task type |
|
|
62
64
|
|---|---|---|---|
|
|
63
|
-
| `Text2Text` | text to text | any causal LM in the transformers layout, via `AutoModelForCausalLM` | `
|
|
64
|
-
| `Text2Image` | text (and image) to image | any diffusers pipeline | `
|
|
65
|
-
| `Image2Mesh` — a base: subclass it with the model's `load` and `make_mesh` | image to mesh | the model the subclass loads, behind the task's `
|
|
66
|
-
| `TextRewriter` | text to text, rewritten | any encoder-decoder LM (T5, BART, Marian, ...) in the transformers layout, via `AutoModelForSeq2SeqLM` | `
|
|
67
|
-
| `AnthropicText2Text` | text to text | the Anthropic API, forwarded from the cluster | `
|
|
68
|
-
| `GeminiText2Text` | text to text | the Gemini API, forwarded from the cluster | `
|
|
69
|
-
| `GeminiText2Image` | text to image | the Gemini API, forwarded from the cluster | `
|
|
65
|
+
| `Text2Text` | text to text | any causal LM in the transformers layout, via `AutoModelForCausalLM` | `CompletingModel` (`complete`), plus its own `loglikelihoods` and `last_hidden_states` |
|
|
66
|
+
| `Text2Image` | text (and image) to image | any diffusers pipeline | `GeneratingModel` (`generate`) |
|
|
67
|
+
| `Image2Mesh` — a base: subclass it with the model's `load` and `make_mesh` | image to mesh | the model the subclass loads, behind the task's `mesh` endpoint | `MeshingModel` (`mesh`: one picture in, a `GeneratedMesh` out) |
|
|
68
|
+
| `TextRewriter` | text to text, rewritten | any encoder-decoder LM (T5, BART, Marian, ...) in the transformers layout, via `AutoModelForSeq2SeqLM` | `RewritingModel` (`rewrite`: one text in, one text out) |
|
|
69
|
+
| `AnthropicText2Text` | text to text | the Anthropic API, forwarded from the cluster | `CompletingModel` (`complete`) |
|
|
70
|
+
| `GeminiText2Text` | text to text | the Gemini API, forwarded from the cluster | `CompletingModel` (`complete`) |
|
|
71
|
+
| `GeminiText2Image` | text to image | the Gemini API, forwarded from the cluster | `GeneratingModel` (`generate`) |
|
|
70
72
|
|
|
71
73
|
| Importer | Source |
|
|
72
74
|
|---|---|
|
|
@@ -89,8 +91,9 @@ need:
|
|
|
89
91
|
|
|
90
92
|
One with weights goes through `cortexgrid.import_model`, one without through
|
|
91
93
|
`cortexgrid.register_model`. Once deployed, the `cortexgrid.Deployment` hands back
|
|
92
|
-
the client that
|
|
93
|
-
|
|
94
|
+
the client that calls the app's endpoints, typed as the serve app and so as its task
|
|
95
|
+
type: `deployment.client()` once the app serves, or `deployment.client_async()` at
|
|
96
|
+
once, with `await model.is_ready()` to ask.
|
|
94
97
|
|
|
95
98
|
## Quick start
|
|
96
99
|
|
|
@@ -122,7 +125,7 @@ cortexgrid.remote(
|
|
|
122
125
|
deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED, timeout=1800)
|
|
123
126
|
|
|
124
127
|
# 3. Inference — the client blocks until the Ray Serve app is running.
|
|
125
|
-
model = deployment.client()
|
|
128
|
+
model: mg.GeneratingModel = deployment.client()
|
|
126
129
|
|
|
127
130
|
async def run():
|
|
128
131
|
result = await mg.generate(model, "a red bicycle on a beach at sunrise")
|
|
@@ -277,10 +280,13 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
|
|
|
277
280
|
|
|
278
281
|
## Inference
|
|
279
282
|
|
|
280
|
-
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`)
|
|
283
|
+
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`).
|
|
284
|
+
`tools` are Python functions or tool specs; a function goes to the model as its spec,
|
|
285
|
+
and each `ToolCall` comes back as data (`.id`, `.name`, `.arguments`) for the caller
|
|
286
|
+
to run:
|
|
281
287
|
|
|
282
288
|
```python
|
|
283
|
-
model = deployment.client()
|
|
289
|
+
model: mg.CompletingModel = deployment.client()
|
|
284
290
|
messages = [{"role": "user", "content": "Explain RAG in one sentence."}]
|
|
285
291
|
|
|
286
292
|
async for chunk in mg.complete(model, messages, max_new_tokens=512, temperature=0.7):
|
|
@@ -321,9 +327,9 @@ a re-deploy rather than a re-registration.
|
|
|
321
327
|
|
|
322
328
|
A replica asks for no hardware at all, so it is placed on any node, CPU-only
|
|
323
329
|
included. From there it deploys and streams exactly like a cluster-served model:
|
|
324
|
-
`deployment.client()`
|
|
330
|
+
`deployment.client()` is a `CompletingModel` like `Text2Text`'s. Anthropic
|
|
325
331
|
reports tool calls as structured blocks rather than as generated text, so the
|
|
326
|
-
serve app
|
|
332
|
+
serve app turns them into `ToolCall`s as they come.
|
|
327
333
|
|
|
328
334
|
### Gemini
|
|
329
335
|
|
|
@@ -345,16 +351,14 @@ cortexgrid.register_model(
|
|
|
345
351
|
Everything said above for Anthropic holds: `config` carries the model name and
|
|
346
352
|
the name of the secret (`api_key_secret="..."` to use another), both editable on
|
|
347
353
|
the model card; a replica needs no hardware; and `deployment.client()`
|
|
348
|
-
is a `
|
|
349
|
-
text form the client parses.
|
|
354
|
+
is a `CompletingModel`, with Gemini's function calls turned into `ToolCall`s.
|
|
350
355
|
|
|
351
|
-
`GeminiText2Image`
|
|
352
|
-
client is a `ServedGeneratingModel`:
|
|
356
|
+
`GeminiText2Image` is a `GeneratingModel` like `Text2Image`:
|
|
353
357
|
|
|
354
358
|
```python
|
|
355
359
|
entry = mg.Hosted("gemini-2.5-flash-image", mg.GeminiText2Image)
|
|
356
360
|
...
|
|
357
|
-
model = deployment.client()
|
|
361
|
+
model: mg.GeneratingModel = deployment.client()
|
|
358
362
|
picture = await mg.generate(model, "a fox in the snow", size=2048)
|
|
359
363
|
```
|
|
360
364
|
|
|
@@ -373,14 +377,11 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
|
|
|
373
377
|
| `Text2Text`, `Text2Image`, `TextRewriter`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
|
|
374
378
|
| `AnthropicText2Text` | Serve app that forwards to the Anthropic API (a `HostedModel`). |
|
|
375
379
|
| `GeminiText2Text`, `GeminiText2Image` | Serve apps that forward to the Gemini API (`HostedModel`s sharing the `GeminiModel` base). |
|
|
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)
|
|
377
|
-
| `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()
|
|
380
|
+
| `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). |
|
|
381
|
+
| `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`. |
|
|
378
382
|
| `Weights(params=None, file_bytes=0)` | What an importer reads about the weights, handed to the serve app to size a replica. |
|
|
379
|
-
| `complete(model, messages, tools=None, max_new_tokens=
|
|
380
|
-
| `generate(model, prompt, *, image=None,
|
|
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. |
|
|
383
|
+
| `complete(model, messages, tools=None, max_new_tokens=None, temperature=0.7)` | Async stream of `CompletionChunk` for a `CompletingModel`. |
|
|
384
|
+
| `generate(model, prompt, *, image=None, steps=None, guidance=None, size=1024, seed=None) -> GeneratedImage` | One image from a `GeneratingModel`. |
|
|
384
385
|
| `split_model_id(model_id) -> (family, suffix)` | The registry identity an importer derives. |
|
|
385
386
|
| `detect_device()` | The torch device a serve app should load onto. |
|
|
386
387
|
| `compiling.Mode.GRAPHED` / `compiling.Mode.FUSED` | The two modes a serve app chooses between. See [Compilation](#compilation). |
|
|
@@ -388,19 +389,19 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
|
|
|
388
389
|
| `ModelDeployFailed` | Re-exported from cortexgrid; subclasses `RuntimeError`. |
|
|
389
390
|
|
|
390
391
|
Types: `CompletingModel`, `GeneratingModel`, `MeshingModel`, `RewritingModel` (each
|
|
391
|
-
|
|
392
|
-
`
|
|
392
|
+
the abstract base of its task's serve apps, declaring the task's endpoints),
|
|
393
|
+
`CompletionChunk`, `GeneratedImage`, `GeneratedMesh`, `ToolCall`, `Message`, `Tool`,
|
|
394
|
+
`ToolSpec`, `Tensor`.
|
|
393
395
|
|
|
394
396
|
Extending it:
|
|
395
397
|
|
|
396
398
|
- **A new source** is one importer: subclass `Importer` with `download(local_dir)`
|
|
397
399
|
and `weights()`, and every serve app whose weight format it delivers runs its
|
|
398
400
|
models unchanged.
|
|
399
|
-
- **A new hosted API** is one serve app: subclass `HostedModel
|
|
400
|
-
with `Hosted`. A text-to-text one
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
then `ServedCompletingModel` is its client, unchanged.
|
|
401
|
+
- **A new hosted API** is one serve app: subclass `HostedModel` and the task's type,
|
|
402
|
+
and register it with `Hosted`. A text-to-text one subclasses `CompletingModel` and
|
|
403
|
+
streams `CompletionChunk`s from its `complete` endpoint, with
|
|
404
|
+
`StreamedCompletionWriter` turning the upstream's text and tool calls into them.
|
|
404
405
|
- **A model no task's serve app can load** gets its own serve app — an `Image2Mesh`
|
|
405
406
|
subclass, say, with `load` and `make_mesh`, and `compile` if compiling it pays —
|
|
406
407
|
and, if its source is unusual too, its own importer.
|
|
@@ -22,23 +22,24 @@ Upgrading from 0.2.x? See [MIGRATION.md](https://github.com/robodatalab/cortexgr
|
|
|
22
22
|
A model is two things, and the library keeps them apart:
|
|
23
23
|
|
|
24
24
|
- **What runs it** — a serve app in `cortexgrid_infer.serve_apps`, named for the task
|
|
25
|
-
it runs, not for where the model came from. It loads the weights
|
|
26
|
-
|
|
27
|
-
them. One serve app runs every model of its task,
|
|
25
|
+
it runs, not for where the model came from. It loads the weights and serves the
|
|
26
|
+
task's endpoints, declared by the task's type (`CompletingModel`, say); cortexgrid
|
|
27
|
+
generates the client that calls them. One serve app runs every model of its task,
|
|
28
|
+
whichever source staged it.
|
|
28
29
|
- **Where its weights come from** — an importer in `cortexgrid_infer.importers`, one
|
|
29
30
|
per source. It names the model in the registry, downloads the weights, and sizes
|
|
30
31
|
them from the source's metadata. It is handed the serve app, so one importer takes
|
|
31
32
|
any model its source holds.
|
|
32
33
|
|
|
33
|
-
| Serve app | Task | Runs |
|
|
34
|
+
| Serve app | Task | Runs | Task type |
|
|
34
35
|
|---|---|---|---|
|
|
35
|
-
| `Text2Text` | text to text | any causal LM in the transformers layout, via `AutoModelForCausalLM` | `
|
|
36
|
-
| `Text2Image` | text (and image) to image | any diffusers pipeline | `
|
|
37
|
-
| `Image2Mesh` — a base: subclass it with the model's `load` and `make_mesh` | image to mesh | the model the subclass loads, behind the task's `
|
|
38
|
-
| `TextRewriter` | text to text, rewritten | any encoder-decoder LM (T5, BART, Marian, ...) in the transformers layout, via `AutoModelForSeq2SeqLM` | `
|
|
39
|
-
| `AnthropicText2Text` | text to text | the Anthropic API, forwarded from the cluster | `
|
|
40
|
-
| `GeminiText2Text` | text to text | the Gemini API, forwarded from the cluster | `
|
|
41
|
-
| `GeminiText2Image` | text to image | the Gemini API, forwarded from the cluster | `
|
|
36
|
+
| `Text2Text` | text to text | any causal LM in the transformers layout, via `AutoModelForCausalLM` | `CompletingModel` (`complete`), plus its own `loglikelihoods` and `last_hidden_states` |
|
|
37
|
+
| `Text2Image` | text (and image) to image | any diffusers pipeline | `GeneratingModel` (`generate`) |
|
|
38
|
+
| `Image2Mesh` — a base: subclass it with the model's `load` and `make_mesh` | image to mesh | the model the subclass loads, behind the task's `mesh` endpoint | `MeshingModel` (`mesh`: one picture in, a `GeneratedMesh` out) |
|
|
39
|
+
| `TextRewriter` | text to text, rewritten | any encoder-decoder LM (T5, BART, Marian, ...) in the transformers layout, via `AutoModelForSeq2SeqLM` | `RewritingModel` (`rewrite`: one text in, one text out) |
|
|
40
|
+
| `AnthropicText2Text` | text to text | the Anthropic API, forwarded from the cluster | `CompletingModel` (`complete`) |
|
|
41
|
+
| `GeminiText2Text` | text to text | the Gemini API, forwarded from the cluster | `CompletingModel` (`complete`) |
|
|
42
|
+
| `GeminiText2Image` | text to image | the Gemini API, forwarded from the cluster | `GeneratingModel` (`generate`) |
|
|
42
43
|
|
|
43
44
|
| Importer | Source |
|
|
44
45
|
|---|---|
|
|
@@ -61,8 +62,9 @@ need:
|
|
|
61
62
|
|
|
62
63
|
One with weights goes through `cortexgrid.import_model`, one without through
|
|
63
64
|
`cortexgrid.register_model`. Once deployed, the `cortexgrid.Deployment` hands back
|
|
64
|
-
the client that
|
|
65
|
-
|
|
65
|
+
the client that calls the app's endpoints, typed as the serve app and so as its task
|
|
66
|
+
type: `deployment.client()` once the app serves, or `deployment.client_async()` at
|
|
67
|
+
once, with `await model.is_ready()` to ask.
|
|
66
68
|
|
|
67
69
|
## Quick start
|
|
68
70
|
|
|
@@ -94,7 +96,7 @@ cortexgrid.remote(
|
|
|
94
96
|
deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED, timeout=1800)
|
|
95
97
|
|
|
96
98
|
# 3. Inference — the client blocks until the Ray Serve app is running.
|
|
97
|
-
model = deployment.client()
|
|
99
|
+
model: mg.GeneratingModel = deployment.client()
|
|
98
100
|
|
|
99
101
|
async def run():
|
|
100
102
|
result = await mg.generate(model, "a red bicycle on a beach at sunrise")
|
|
@@ -249,10 +251,13 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
|
|
|
249
251
|
|
|
250
252
|
## Inference
|
|
251
253
|
|
|
252
|
-
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`)
|
|
254
|
+
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`).
|
|
255
|
+
`tools` are Python functions or tool specs; a function goes to the model as its spec,
|
|
256
|
+
and each `ToolCall` comes back as data (`.id`, `.name`, `.arguments`) for the caller
|
|
257
|
+
to run:
|
|
253
258
|
|
|
254
259
|
```python
|
|
255
|
-
model = deployment.client()
|
|
260
|
+
model: mg.CompletingModel = deployment.client()
|
|
256
261
|
messages = [{"role": "user", "content": "Explain RAG in one sentence."}]
|
|
257
262
|
|
|
258
263
|
async for chunk in mg.complete(model, messages, max_new_tokens=512, temperature=0.7):
|
|
@@ -293,9 +298,9 @@ a re-deploy rather than a re-registration.
|
|
|
293
298
|
|
|
294
299
|
A replica asks for no hardware at all, so it is placed on any node, CPU-only
|
|
295
300
|
included. From there it deploys and streams exactly like a cluster-served model:
|
|
296
|
-
`deployment.client()`
|
|
301
|
+
`deployment.client()` is a `CompletingModel` like `Text2Text`'s. Anthropic
|
|
297
302
|
reports tool calls as structured blocks rather than as generated text, so the
|
|
298
|
-
serve app
|
|
303
|
+
serve app turns them into `ToolCall`s as they come.
|
|
299
304
|
|
|
300
305
|
### Gemini
|
|
301
306
|
|
|
@@ -317,16 +322,14 @@ cortexgrid.register_model(
|
|
|
317
322
|
Everything said above for Anthropic holds: `config` carries the model name and
|
|
318
323
|
the name of the secret (`api_key_secret="..."` to use another), both editable on
|
|
319
324
|
the model card; a replica needs no hardware; and `deployment.client()`
|
|
320
|
-
is a `
|
|
321
|
-
text form the client parses.
|
|
325
|
+
is a `CompletingModel`, with Gemini's function calls turned into `ToolCall`s.
|
|
322
326
|
|
|
323
|
-
`GeminiText2Image`
|
|
324
|
-
client is a `ServedGeneratingModel`:
|
|
327
|
+
`GeminiText2Image` is a `GeneratingModel` like `Text2Image`:
|
|
325
328
|
|
|
326
329
|
```python
|
|
327
330
|
entry = mg.Hosted("gemini-2.5-flash-image", mg.GeminiText2Image)
|
|
328
331
|
...
|
|
329
|
-
model = deployment.client()
|
|
332
|
+
model: mg.GeneratingModel = deployment.client()
|
|
330
333
|
picture = await mg.generate(model, "a fox in the snow", size=2048)
|
|
331
334
|
```
|
|
332
335
|
|
|
@@ -345,14 +348,11 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
|
|
|
345
348
|
| `Text2Text`, `Text2Image`, `TextRewriter`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
|
|
346
349
|
| `AnthropicText2Text` | Serve app that forwards to the Anthropic API (a `HostedModel`). |
|
|
347
350
|
| `GeminiText2Text`, `GeminiText2Image` | Serve apps that forward to the Gemini API (`HostedModel`s sharing the `GeminiModel` base). |
|
|
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)
|
|
349
|
-
| `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()
|
|
351
|
+
| `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). |
|
|
352
|
+
| `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`. |
|
|
350
353
|
| `Weights(params=None, file_bytes=0)` | What an importer reads about the weights, handed to the serve app to size a replica. |
|
|
351
|
-
| `complete(model, messages, tools=None, max_new_tokens=
|
|
352
|
-
| `generate(model, prompt, *, image=None,
|
|
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. |
|
|
354
|
+
| `complete(model, messages, tools=None, max_new_tokens=None, temperature=0.7)` | Async stream of `CompletionChunk` for a `CompletingModel`. |
|
|
355
|
+
| `generate(model, prompt, *, image=None, steps=None, guidance=None, size=1024, seed=None) -> GeneratedImage` | One image from a `GeneratingModel`. |
|
|
356
356
|
| `split_model_id(model_id) -> (family, suffix)` | The registry identity an importer derives. |
|
|
357
357
|
| `detect_device()` | The torch device a serve app should load onto. |
|
|
358
358
|
| `compiling.Mode.GRAPHED` / `compiling.Mode.FUSED` | The two modes a serve app chooses between. See [Compilation](#compilation). |
|
|
@@ -360,19 +360,19 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
|
|
|
360
360
|
| `ModelDeployFailed` | Re-exported from cortexgrid; subclasses `RuntimeError`. |
|
|
361
361
|
|
|
362
362
|
Types: `CompletingModel`, `GeneratingModel`, `MeshingModel`, `RewritingModel` (each
|
|
363
|
-
|
|
364
|
-
`
|
|
363
|
+
the abstract base of its task's serve apps, declaring the task's endpoints),
|
|
364
|
+
`CompletionChunk`, `GeneratedImage`, `GeneratedMesh`, `ToolCall`, `Message`, `Tool`,
|
|
365
|
+
`ToolSpec`, `Tensor`.
|
|
365
366
|
|
|
366
367
|
Extending it:
|
|
367
368
|
|
|
368
369
|
- **A new source** is one importer: subclass `Importer` with `download(local_dir)`
|
|
369
370
|
and `weights()`, and every serve app whose weight format it delivers runs its
|
|
370
371
|
models unchanged.
|
|
371
|
-
- **A new hosted API** is one serve app: subclass `HostedModel
|
|
372
|
-
with `Hosted`. A text-to-text one
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
then `ServedCompletingModel` is its client, unchanged.
|
|
372
|
+
- **A new hosted API** is one serve app: subclass `HostedModel` and the task's type,
|
|
373
|
+
and register it with `Hosted`. A text-to-text one subclasses `CompletingModel` and
|
|
374
|
+
streams `CompletionChunk`s from its `complete` endpoint, with
|
|
375
|
+
`StreamedCompletionWriter` turning the upstream's text and tool calls into them.
|
|
376
376
|
- **A model no task's serve app can load** gets its own serve app — an `Image2Mesh`
|
|
377
377
|
subclass, say, with `load` and `make_mesh`, and `compile` if compiling it pays —
|
|
378
378
|
and, if its source is unusual too, its own importer.
|
|
@@ -7,9 +7,9 @@ nothing else, split along the two things a model is:
|
|
|
7
7
|
|
|
8
8
|
- where its weights come from - an importer per source (`importers`), which
|
|
9
9
|
names, fetches and sizes them;
|
|
10
|
-
- what runs it - a serve app per task (`serve_apps`), which loads the weights
|
|
11
|
-
|
|
12
|
-
them.
|
|
10
|
+
- what runs it - a serve app per task (`serve_apps`), which loads the weights
|
|
11
|
+
and serves the task's endpoints, declared by the task's type in `core`
|
|
12
|
+
(`CompletingModel`, say); cortexgrid generates the client that calls them.
|
|
13
13
|
|
|
14
14
|
imp = cortexgrid_infer.HuggingFaceImporter("Qwen/Qwen2.5-0.5B-Instruct", cortexgrid_infer.Text2Text)
|
|
15
15
|
with imp:
|
|
@@ -19,7 +19,7 @@ nothing else, split along the two things a model is:
|
|
|
19
19
|
requirements=imp.requirements(),
|
|
20
20
|
)
|
|
21
21
|
deployment = cortexgrid.deploy_model(imp.family, imp.suffix, cortexgrid.IMPORTED)
|
|
22
|
-
model = deployment.client()
|
|
22
|
+
model: cortexgrid_infer.CompletingModel = 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.
|
|
@@ -38,6 +38,7 @@ from cortexgrid_infer.core import (
|
|
|
38
38
|
MeshingModel,
|
|
39
39
|
Message,
|
|
40
40
|
RewritingModel,
|
|
41
|
+
Tensor,
|
|
41
42
|
Tool,
|
|
42
43
|
ToolCall,
|
|
43
44
|
ToolSpec,
|
|
@@ -46,12 +47,6 @@ from cortexgrid_infer.core import (
|
|
|
46
47
|
mesh,
|
|
47
48
|
rewrite,
|
|
48
49
|
)
|
|
49
|
-
from cortexgrid_infer.protocols import (
|
|
50
|
-
ServedCompletingModel,
|
|
51
|
-
ServedGeneratingModel,
|
|
52
|
-
ServedMeshingModel,
|
|
53
|
-
ServedRewritingModel,
|
|
54
|
-
)
|
|
55
50
|
from cortexgrid_infer.device import detect_device
|
|
56
51
|
from cortexgrid_infer.serve_apps import (
|
|
57
52
|
AnthropicText2Text,
|
|
@@ -77,6 +72,7 @@ __all__ = [
|
|
|
77
72
|
"MeshingModel",
|
|
78
73
|
"RewritingModel",
|
|
79
74
|
"Message",
|
|
75
|
+
"Tensor",
|
|
80
76
|
"Tool",
|
|
81
77
|
"ToolCall",
|
|
82
78
|
"ToolSpec",
|
|
@@ -86,10 +82,6 @@ __all__ = [
|
|
|
86
82
|
"rewrite",
|
|
87
83
|
"ModelDeployFailed",
|
|
88
84
|
"detect_device",
|
|
89
|
-
"ServedCompletingModel",
|
|
90
|
-
"ServedGeneratingModel",
|
|
91
|
-
"ServedMeshingModel",
|
|
92
|
-
"ServedRewritingModel",
|
|
93
85
|
"LocalModel",
|
|
94
86
|
"HostedModel",
|
|
95
87
|
"Weights",
|
|
@@ -3,13 +3,24 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import abc
|
|
6
|
+
import base64
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
|
-
from functools import partial
|
|
8
8
|
from collections.abc import AsyncIterator
|
|
9
|
-
from
|
|
9
|
+
from functools import partial
|
|
10
|
+
from typing import Annotated, Any
|
|
10
11
|
|
|
11
|
-
import cortexgrid
|
|
12
12
|
import numpy as np
|
|
13
|
+
import torch
|
|
14
|
+
from pydantic import Base64Bytes, PlainSerializer, PlainValidator
|
|
15
|
+
|
|
16
|
+
from cortexgrid_infer.utils import Tool, ToolSpec
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Tensor = Annotated[
|
|
20
|
+
torch.Tensor,
|
|
21
|
+
PlainValidator(torch.tensor),
|
|
22
|
+
PlainSerializer(torch.Tensor.tolist),
|
|
23
|
+
]
|
|
13
24
|
|
|
14
25
|
|
|
15
26
|
@dataclass
|
|
@@ -17,10 +28,6 @@ class ToolCall:
|
|
|
17
28
|
id: str
|
|
18
29
|
name: str
|
|
19
30
|
arguments: dict[str, Any]
|
|
20
|
-
_func: partial[Any] = field(repr=False)
|
|
21
|
-
|
|
22
|
-
def __call__(self) -> Any:
|
|
23
|
-
return self._func(**self.arguments)
|
|
24
31
|
|
|
25
32
|
|
|
26
33
|
@dataclass
|
|
@@ -38,42 +45,18 @@ class CompletionChunk:
|
|
|
38
45
|
def has_tool_calls(self) -> bool:
|
|
39
46
|
return bool(self.tool_calls)
|
|
40
47
|
|
|
41
|
-
@classmethod
|
|
42
|
-
def from_wire(
|
|
43
|
-
cls, answered: dict[str, Any], tools_by_name: dict[str, Callable[..., Any]]
|
|
44
|
-
) -> CompletionChunk:
|
|
45
|
-
tool_calls = []
|
|
46
|
-
for called in answered["tool_calls"]:
|
|
47
|
-
tool = tools_by_name.get(called["name"])
|
|
48
|
-
if tool is None:
|
|
49
|
-
continue
|
|
50
|
-
bound = partial(tool, **called["arguments"])
|
|
51
|
-
tool_call = ToolCall(
|
|
52
|
-
id=called["id"], name=called["name"], arguments=called["arguments"], _func=bound
|
|
53
|
-
)
|
|
54
|
-
tool_calls.append(tool_call)
|
|
55
|
-
return cls(
|
|
56
|
-
content=answered["content"],
|
|
57
|
-
thinking=answered["thinking"],
|
|
58
|
-
tool_calls=tool_calls,
|
|
59
|
-
finish_reason=answered["finish_reason"],
|
|
60
|
-
)
|
|
61
|
-
|
|
62
48
|
|
|
63
49
|
Message = dict[str, Any]
|
|
64
|
-
ToolSpec = dict[str, Any]
|
|
65
|
-
Tool = Callable[..., Any] | ToolSpec
|
|
66
50
|
|
|
67
51
|
|
|
68
|
-
class CompletingModel(
|
|
52
|
+
class CompletingModel(abc.ABC):
|
|
69
53
|
@abc.abstractmethod
|
|
70
54
|
def complete(
|
|
71
55
|
self,
|
|
72
56
|
messages: list[Message],
|
|
73
|
-
tools:
|
|
74
|
-
max_new_tokens: int =
|
|
57
|
+
tools: list[Tool] | None = None,
|
|
58
|
+
max_new_tokens: int | None = None,
|
|
75
59
|
temperature: float = 0.7,
|
|
76
|
-
**kwargs: Any,
|
|
77
60
|
) -> AsyncIterator[CompletionChunk]: ...
|
|
78
61
|
|
|
79
62
|
|
|
@@ -82,13 +65,13 @@ class GeneratedImage:
|
|
|
82
65
|
"""One generated image: the encoded file bytes (PNG) plus the resolved
|
|
83
66
|
parameters the deployment used to produce it."""
|
|
84
67
|
|
|
85
|
-
image:
|
|
68
|
+
image: Base64Bytes
|
|
86
69
|
width: int
|
|
87
70
|
height: int
|
|
88
71
|
params: dict[str, Any] = field(default_factory=dict)
|
|
89
72
|
|
|
90
73
|
|
|
91
|
-
class GeneratingModel(
|
|
74
|
+
class GeneratingModel(abc.ABC):
|
|
92
75
|
"""A deployed image model. Unlike completion, generation is a single
|
|
93
76
|
request/response (no token streaming), so `generate` returns one result."""
|
|
94
77
|
|
|
@@ -97,15 +80,43 @@ class GeneratingModel(cortexgrid.DeploymentClient, abc.ABC):
|
|
|
97
80
|
self,
|
|
98
81
|
prompt: str,
|
|
99
82
|
*,
|
|
100
|
-
image:
|
|
83
|
+
image: Base64Bytes | None = None,
|
|
101
84
|
steps: int | None = None,
|
|
102
85
|
guidance: float | None = None,
|
|
103
86
|
size: int = 1024,
|
|
104
87
|
seed: int | None = None,
|
|
105
|
-
**kwargs: Any,
|
|
106
88
|
) -> GeneratedImage: ...
|
|
107
89
|
|
|
108
90
|
|
|
91
|
+
def _encoded_array(array: np.ndarray, dtype: str) -> str:
|
|
92
|
+
contiguous = np.ascontiguousarray(array, dtype=dtype)
|
|
93
|
+
encoded = base64.b64encode(contiguous.tobytes())
|
|
94
|
+
return encoded.decode("ascii")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _decoded_array(data: str, dtype: str, columns: int) -> np.ndarray:
|
|
98
|
+
raw = base64.b64decode(data)
|
|
99
|
+
flat = np.frombuffer(raw, dtype=dtype)
|
|
100
|
+
return flat.reshape(-1, columns)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
Vertices = Annotated[
|
|
104
|
+
np.ndarray,
|
|
105
|
+
PlainValidator(partial(_decoded_array, dtype="<f4", columns=3)),
|
|
106
|
+
PlainSerializer(partial(_encoded_array, dtype="<f4")),
|
|
107
|
+
]
|
|
108
|
+
Faces = Annotated[
|
|
109
|
+
np.ndarray,
|
|
110
|
+
PlainValidator(partial(_decoded_array, dtype="<i4", columns=3)),
|
|
111
|
+
PlainSerializer(partial(_encoded_array, dtype="<i4")),
|
|
112
|
+
]
|
|
113
|
+
Colours = Annotated[
|
|
114
|
+
np.ndarray,
|
|
115
|
+
PlainValidator(partial(_decoded_array, dtype="u1", columns=3)),
|
|
116
|
+
PlainSerializer(partial(_encoded_array, dtype="u1")),
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
|
|
109
120
|
@dataclass
|
|
110
121
|
class GeneratedMesh:
|
|
111
122
|
"""One mesh: `vertices` (N, 3) float32, x to the right of the picture it was
|
|
@@ -113,35 +124,36 @@ class GeneratedMesh:
|
|
|
113
124
|
into them, counter-clockwise seen from outside; `colours` (N, 3) uint8, each
|
|
114
125
|
vertex's colour. Plus the resolved parameters the deployment used."""
|
|
115
126
|
|
|
116
|
-
vertices:
|
|
117
|
-
faces:
|
|
118
|
-
colours:
|
|
127
|
+
vertices: Vertices
|
|
128
|
+
faces: Faces
|
|
129
|
+
colours: Colours
|
|
119
130
|
params: dict[str, Any] = field(default_factory=dict)
|
|
120
131
|
|
|
121
132
|
|
|
122
|
-
class MeshingModel(
|
|
133
|
+
class MeshingModel(abc.ABC):
|
|
123
134
|
"""A deployed model that makes a mesh of the object in one picture."""
|
|
124
135
|
|
|
125
136
|
@abc.abstractmethod
|
|
126
|
-
async def mesh(
|
|
137
|
+
async def mesh(
|
|
138
|
+
self, image: Base64Bytes, options: dict[str, Any] | None = None
|
|
139
|
+
) -> GeneratedMesh: ...
|
|
127
140
|
|
|
128
141
|
|
|
129
|
-
class RewritingModel(
|
|
142
|
+
class RewritingModel(abc.ABC):
|
|
130
143
|
"""A deployed model that rewrites one text into another - corrects,
|
|
131
144
|
paraphrases, summarises or translates it. Like generation, a single
|
|
132
145
|
request/response."""
|
|
133
146
|
|
|
134
147
|
@abc.abstractmethod
|
|
135
|
-
async def rewrite(self, text: str, max_new_tokens: int = 512
|
|
148
|
+
async def rewrite(self, text: str, max_new_tokens: int = 512) -> str: ...
|
|
136
149
|
|
|
137
150
|
|
|
138
151
|
async def complete(
|
|
139
152
|
deployed_model: CompletingModel,
|
|
140
153
|
messages: list[Message],
|
|
141
|
-
tools:
|
|
142
|
-
max_new_tokens: int =
|
|
154
|
+
tools: list[Tool] | None = None,
|
|
155
|
+
max_new_tokens: int | None = None,
|
|
143
156
|
temperature: float = 0.7,
|
|
144
|
-
**kwargs: Any,
|
|
145
157
|
) -> AsyncIterator[CompletionChunk]:
|
|
146
158
|
"""Async completion request to the deployed model."""
|
|
147
159
|
async for chunk in deployed_model.complete(
|
|
@@ -149,7 +161,6 @@ async def complete(
|
|
|
149
161
|
tools,
|
|
150
162
|
max_new_tokens=max_new_tokens,
|
|
151
163
|
temperature=temperature,
|
|
152
|
-
**kwargs,
|
|
153
164
|
):
|
|
154
165
|
yield chunk
|
|
155
166
|
|
|
@@ -159,22 +170,28 @@ async def generate(
|
|
|
159
170
|
prompt: str,
|
|
160
171
|
*,
|
|
161
172
|
image: bytes | None = None,
|
|
162
|
-
|
|
173
|
+
steps: int | None = None,
|
|
174
|
+
guidance: float | None = None,
|
|
175
|
+
size: int = 1024,
|
|
176
|
+
seed: int | None = None,
|
|
163
177
|
) -> GeneratedImage:
|
|
164
178
|
"""Single image-generation request to the deployed model."""
|
|
165
|
-
return await deployed_model.generate(
|
|
179
|
+
return await deployed_model.generate(
|
|
180
|
+
prompt, image=image, steps=steps, guidance=guidance, size=size, seed=seed
|
|
181
|
+
)
|
|
166
182
|
|
|
167
183
|
|
|
168
|
-
async def mesh(
|
|
184
|
+
async def mesh(
|
|
185
|
+
deployed_model: MeshingModel, image: bytes, options: dict[str, Any] | None = None
|
|
186
|
+
) -> GeneratedMesh:
|
|
169
187
|
"""Single image-to-mesh request to the deployed model."""
|
|
170
|
-
return await deployed_model.mesh(image,
|
|
188
|
+
return await deployed_model.mesh(image, options)
|
|
171
189
|
|
|
172
190
|
|
|
173
191
|
async def rewrite(
|
|
174
192
|
deployed_model: RewritingModel,
|
|
175
193
|
text: str,
|
|
176
194
|
max_new_tokens: int = 512,
|
|
177
|
-
**kwargs: Any,
|
|
178
195
|
) -> str:
|
|
179
196
|
"""Single rewriting request to the deployed model."""
|
|
180
|
-
return await deployed_model.rewrite(text, max_new_tokens=max_new_tokens
|
|
197
|
+
return await deployed_model.rewrite(text, max_new_tokens=max_new_tokens)
|