cortexgrid-infer 0.3.3__tar.gz → 0.3.5__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.3 → cortexgrid_infer-0.3.5}/PKG-INFO +43 -13
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/README.md +42 -12
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/core.py +1 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/importers/base.py +3 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/protocols/completion.py +50 -20
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/base.py +12 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/base.py +1 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/text2text.py +28 -3
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/image2mesh.py +18 -1
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/text2image.py +7 -3
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/text2text.py +13 -2
- cortexgrid_infer-0.3.5/cortexgrid_infer/serve_apps/text_rewriter.py +107 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/pyproject.toml +1 -1
- cortexgrid_infer-0.3.3/cortexgrid_infer/serve_apps/text_rewriter.py +0 -54
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/.gitignore +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/LICENSE +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/compiling.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/device.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/importers/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/importers/huggingface.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/protocols/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/protocols/imaging.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/protocols/meshing.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/protocols/rewriting.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/py.typed +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/registry.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/anthropic.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/text2image.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/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.
|
|
3
|
+
Version: 0.3.5
|
|
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
|
|
@@ -178,7 +178,23 @@ beyond the weights. (A `Hosted` entry asks for none — it holds no weights.)
|
|
|
178
178
|
|
|
179
179
|
## Compilation
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
Every serve app that runs weights can run them through `torch.compile`, and none
|
|
182
|
+
does unless asked: a replica is eager unless its model card carries
|
|
183
|
+
`compile: "true"`. Each `LocalModel` writes `compile: "false"` onto the card at
|
|
184
|
+
import, so turning it on is an edit of the card and a redeploy:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
config = cortexgrid.model_config(family, suffix, cortexgrid.IMPORTED)
|
|
188
|
+
cortexgrid.set_model_config(family, suffix, cortexgrid.IMPORTED, {**config, "compile": "true"})
|
|
189
|
+
cortexgrid.deploy_model(family, suffix, cortexgrid.IMPORTED, wait=True)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
It is off by default because it is not free: every input shape pays a warm-up on
|
|
193
|
+
the request that first brings it, and each CUDA graph holds memory of its own. It
|
|
194
|
+
pays back where traffic keeps to a few shapes and the model is small enough for
|
|
195
|
+
launch overhead to matter. A card asking for it off CUDA is served eager.
|
|
196
|
+
|
|
197
|
+
Compiled, a serve app runs `torch.compile` over the model it loads, because
|
|
182
198
|
eager PyTorch bills per *operation* and a transformer forward is mostly glue —
|
|
183
199
|
views, broadcasts, permutes, elementwise adds — with only a small fraction of the
|
|
184
200
|
dispatches being the matrix multiplies that do the arithmetic. While the tensors
|
|
@@ -197,7 +213,7 @@ fits the work it does:
|
|
|
197
213
|
| `Mode.GRAPHED` (`reduce-overhead`) | fuses **and** captures a CUDA graph, so a forward replays one graph instead of issuing its launches | a loop of identically shaped forwards, where the capture is reused every iteration. A graph cannot go dynamic, so each distinct shape captures separately |
|
|
198
214
|
| `Mode.FUSED` (`default`) | fuses only | work whose shapes move under it, where there is nothing stable to capture and dynamo is free to compile one dynamic kernel set for all of them |
|
|
199
215
|
|
|
200
|
-
|
|
216
|
+
Each serve app is tuned rather than defaulted:
|
|
201
217
|
|
|
202
218
|
- **`Text2Image`** compiles the denoiser (`transformer` or `unet`)
|
|
203
219
|
and every `text_encoder*` as `Mode.GRAPHED`. A denoising schedule is the ideal case —
|
|
@@ -224,12 +240,25 @@ Both serve apps are tuned rather than defaulted:
|
|
|
224
240
|
chat template puts the turn to answer last — with a warning naming both
|
|
225
241
|
lengths. Serving it whole would resize the cache and recompile the decode loop,
|
|
226
242
|
which costs more than the generation itself.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
243
|
+
- **`TextRewriter`** decodes as `Text2Text` does — a static cache, `Mode.GRAPHED`,
|
|
244
|
+
transformers compiling the step — with the cache sized to the 512 new tokens a
|
|
245
|
+
reply may take, and a larger `max_new_tokens` capped to it. An encoder-decoder
|
|
246
|
+
has a second cache, though: `generate` sizes cross-attention to the encoded
|
|
247
|
+
text, so each distinct text length would capture the decode loop afresh. The
|
|
248
|
+
text is therefore padded up to the next of 32, 64, 128, 256 or 512 tokens —
|
|
249
|
+
one capture per bucket, the padding masked out — and a longer one is cut to
|
|
250
|
+
512, keeping its start, with a warning naming both lengths.
|
|
251
|
+
- **`Image2Mesh`** cannot know what to compile: each subclass brings its own
|
|
252
|
+
model. It calls the subclass's `compile(device)` after `load`, which does
|
|
253
|
+
nothing unless overridden; a subclass compiles its own modules there with
|
|
254
|
+
`compiling.compile_module`, in the mode that fits them.
|
|
255
|
+
|
|
256
|
+
Beyond the switch, the image app asks to be configured for nothing, for the same
|
|
257
|
+
reason none asks which dtype to load in; the completion app takes
|
|
258
|
+
`max_total_tokens` alone, because how much cache a caller needs is the one thing
|
|
259
|
+
the model cannot tell it. All of it happens on CUDA and nowhere else — inductor
|
|
260
|
+
is weakest off it, and a host outrunning its accelerator is a GPU problem to
|
|
261
|
+
begin with.
|
|
233
262
|
|
|
234
263
|
Two things worth knowing:
|
|
235
264
|
|
|
@@ -245,7 +274,7 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
|
|
|
245
274
|
|
|
246
275
|
## Inference
|
|
247
276
|
|
|
248
|
-
`complete` streams `CompletionChunk`s (`.content`, `.tool_calls`, `.finish_reason`):
|
|
277
|
+
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`):
|
|
249
278
|
|
|
250
279
|
```python
|
|
251
280
|
imp = mg.HuggingFaceImporter("Qwen/Qwen2.5-7B-Instruct", mg.Text2Text)
|
|
@@ -338,10 +367,10 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
|
|
|
338
367
|
| `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()`. |
|
|
339
368
|
| `Hosted(model_id, serve_app, **settings)` | Entry for a model hosted elsewhere; no weights. `settings` are the serve app's `config` arguments. |
|
|
340
369
|
| `ModelEntry` | Base of both: `model_id`, `family`, `suffix`, `serve_app`, `requirements()`, `config()`, `client(url)`. |
|
|
341
|
-
| `Text2Text`, `Text2Image`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
|
|
370
|
+
| `Text2Text`, `Text2Image`, `TextRewriter`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
|
|
342
371
|
| `AnthropicText2Text` | Serve app that forwards to the Anthropic API (a `HostedModel`). |
|
|
343
372
|
| `GeminiText2Text`, `GeminiText2Image` | Serve apps that forward to the Gemini API (`HostedModel`s sharing the `GeminiModel` base). |
|
|
344
|
-
| `LocalModel` | Base of the serve apps that run weights: `ignore_patterns()`, `bytes_per_param`, `min_vram_gb`, `requirements(weights)`, `client(url, name)`. |
|
|
373
|
+
| `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)`. |
|
|
345
374
|
| `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`, `client(url, name)`. |
|
|
346
375
|
| `Weights(params=None, file_bytes=0)` | What an importer reads about the weights, handed to the serve app to size a replica. |
|
|
347
376
|
| `complete(model, messages, tools=None, max_new_tokens=2048, temperature=0.7, **kw)` | Async stream of `CompletionChunk` for a `CompletingModel`. |
|
|
@@ -370,7 +399,8 @@ Extending it:
|
|
|
370
399
|
(there is an `encode_tool_call` for upstreams that report them structurally) —
|
|
371
400
|
then `ServedCompletingModel` is its client, unchanged.
|
|
372
401
|
- **A model no task's serve app can load** gets its own serve app — an `Image2Mesh`
|
|
373
|
-
subclass, say
|
|
402
|
+
subclass, say, with `load` and `make_mesh`, and `compile` if compiling it pays —
|
|
403
|
+
and, if its source is unusual too, its own importer.
|
|
374
404
|
|
|
375
405
|
## Notes
|
|
376
406
|
|
|
@@ -150,7 +150,23 @@ beyond the weights. (A `Hosted` entry asks for none — it holds no weights.)
|
|
|
150
150
|
|
|
151
151
|
## Compilation
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
Every serve app that runs weights can run them through `torch.compile`, and none
|
|
154
|
+
does unless asked: a replica is eager unless its model card carries
|
|
155
|
+
`compile: "true"`. Each `LocalModel` writes `compile: "false"` onto the card at
|
|
156
|
+
import, so turning it on is an edit of the card and a redeploy:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
config = cortexgrid.model_config(family, suffix, cortexgrid.IMPORTED)
|
|
160
|
+
cortexgrid.set_model_config(family, suffix, cortexgrid.IMPORTED, {**config, "compile": "true"})
|
|
161
|
+
cortexgrid.deploy_model(family, suffix, cortexgrid.IMPORTED, wait=True)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
It is off by default because it is not free: every input shape pays a warm-up on
|
|
165
|
+
the request that first brings it, and each CUDA graph holds memory of its own. It
|
|
166
|
+
pays back where traffic keeps to a few shapes and the model is small enough for
|
|
167
|
+
launch overhead to matter. A card asking for it off CUDA is served eager.
|
|
168
|
+
|
|
169
|
+
Compiled, a serve app runs `torch.compile` over the model it loads, because
|
|
154
170
|
eager PyTorch bills per *operation* and a transformer forward is mostly glue —
|
|
155
171
|
views, broadcasts, permutes, elementwise adds — with only a small fraction of the
|
|
156
172
|
dispatches being the matrix multiplies that do the arithmetic. While the tensors
|
|
@@ -169,7 +185,7 @@ fits the work it does:
|
|
|
169
185
|
| `Mode.GRAPHED` (`reduce-overhead`) | fuses **and** captures a CUDA graph, so a forward replays one graph instead of issuing its launches | a loop of identically shaped forwards, where the capture is reused every iteration. A graph cannot go dynamic, so each distinct shape captures separately |
|
|
170
186
|
| `Mode.FUSED` (`default`) | fuses only | work whose shapes move under it, where there is nothing stable to capture and dynamo is free to compile one dynamic kernel set for all of them |
|
|
171
187
|
|
|
172
|
-
|
|
188
|
+
Each serve app is tuned rather than defaulted:
|
|
173
189
|
|
|
174
190
|
- **`Text2Image`** compiles the denoiser (`transformer` or `unet`)
|
|
175
191
|
and every `text_encoder*` as `Mode.GRAPHED`. A denoising schedule is the ideal case —
|
|
@@ -196,12 +212,25 @@ Both serve apps are tuned rather than defaulted:
|
|
|
196
212
|
chat template puts the turn to answer last — with a warning naming both
|
|
197
213
|
lengths. Serving it whole would resize the cache and recompile the decode loop,
|
|
198
214
|
which costs more than the generation itself.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
- **`TextRewriter`** decodes as `Text2Text` does — a static cache, `Mode.GRAPHED`,
|
|
216
|
+
transformers compiling the step — with the cache sized to the 512 new tokens a
|
|
217
|
+
reply may take, and a larger `max_new_tokens` capped to it. An encoder-decoder
|
|
218
|
+
has a second cache, though: `generate` sizes cross-attention to the encoded
|
|
219
|
+
text, so each distinct text length would capture the decode loop afresh. The
|
|
220
|
+
text is therefore padded up to the next of 32, 64, 128, 256 or 512 tokens —
|
|
221
|
+
one capture per bucket, the padding masked out — and a longer one is cut to
|
|
222
|
+
512, keeping its start, with a warning naming both lengths.
|
|
223
|
+
- **`Image2Mesh`** cannot know what to compile: each subclass brings its own
|
|
224
|
+
model. It calls the subclass's `compile(device)` after `load`, which does
|
|
225
|
+
nothing unless overridden; a subclass compiles its own modules there with
|
|
226
|
+
`compiling.compile_module`, in the mode that fits them.
|
|
227
|
+
|
|
228
|
+
Beyond the switch, the image app asks to be configured for nothing, for the same
|
|
229
|
+
reason none asks which dtype to load in; the completion app takes
|
|
230
|
+
`max_total_tokens` alone, because how much cache a caller needs is the one thing
|
|
231
|
+
the model cannot tell it. All of it happens on CUDA and nowhere else — inductor
|
|
232
|
+
is weakest off it, and a host outrunning its accelerator is a GPU problem to
|
|
233
|
+
begin with.
|
|
205
234
|
|
|
206
235
|
Two things worth knowing:
|
|
207
236
|
|
|
@@ -217,7 +246,7 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
|
|
|
217
246
|
|
|
218
247
|
## Inference
|
|
219
248
|
|
|
220
|
-
`complete` streams `CompletionChunk`s (`.content`, `.tool_calls`, `.finish_reason`):
|
|
249
|
+
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`):
|
|
221
250
|
|
|
222
251
|
```python
|
|
223
252
|
imp = mg.HuggingFaceImporter("Qwen/Qwen2.5-7B-Instruct", mg.Text2Text)
|
|
@@ -310,10 +339,10 @@ diffusion settings with no Gemini counterpart, and come back as `None`.
|
|
|
310
339
|
| `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()`. |
|
|
311
340
|
| `Hosted(model_id, serve_app, **settings)` | Entry for a model hosted elsewhere; no weights. `settings` are the serve app's `config` arguments. |
|
|
312
341
|
| `ModelEntry` | Base of both: `model_id`, `family`, `suffix`, `serve_app`, `requirements()`, `config()`, `client(url)`. |
|
|
313
|
-
| `Text2Text`, `Text2Image`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
|
|
342
|
+
| `Text2Text`, `Text2Image`, `TextRewriter`, `Image2Mesh` | Serve apps that run weights (`LocalModel`s). |
|
|
314
343
|
| `AnthropicText2Text` | Serve app that forwards to the Anthropic API (a `HostedModel`). |
|
|
315
344
|
| `GeminiText2Text`, `GeminiText2Image` | Serve apps that forward to the Gemini API (`HostedModel`s sharing the `GeminiModel` base). |
|
|
316
|
-
| `LocalModel` | Base of the serve apps that run weights: `ignore_patterns()`, `bytes_per_param`, `min_vram_gb`, `requirements(weights)`, `client(url, name)`. |
|
|
345
|
+
| `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)`. |
|
|
317
346
|
| `HostedModel` | Base of the serve apps that forward: `config(model_id, **settings)`, `requirements()`, `client(url, name)`. |
|
|
318
347
|
| `Weights(params=None, file_bytes=0)` | What an importer reads about the weights, handed to the serve app to size a replica. |
|
|
319
348
|
| `complete(model, messages, tools=None, max_new_tokens=2048, temperature=0.7, **kw)` | Async stream of `CompletionChunk` for a `CompletingModel`. |
|
|
@@ -342,7 +371,8 @@ Extending it:
|
|
|
342
371
|
(there is an `encode_tool_call` for upstreams that report them structurally) —
|
|
343
372
|
then `ServedCompletingModel` is its client, unchanged.
|
|
344
373
|
- **A model no task's serve app can load** gets its own serve app — an `Image2Mesh`
|
|
345
|
-
subclass, say
|
|
374
|
+
subclass, say, with `load` and `make_mesh`, and `compile` if compiling it pays —
|
|
375
|
+
and, if its source is unusual too, its own importer.
|
|
346
376
|
|
|
347
377
|
## Notes
|
|
348
378
|
|
|
@@ -95,5 +95,8 @@ class Importer(ModelEntry):
|
|
|
95
95
|
have, so a hand-set value is never overwritten."""
|
|
96
96
|
return self.serve_app.requirements(self.weights())
|
|
97
97
|
|
|
98
|
+
def config(self) -> dict[str, str]:
|
|
99
|
+
return self.serve_app.config()
|
|
100
|
+
|
|
98
101
|
def client(self, url: str) -> DeployedModel:
|
|
99
102
|
return self.serve_app.client(url, self.model_id)
|
|
@@ -35,6 +35,9 @@ _tool_call_id_counter = itertools.count()
|
|
|
35
35
|
|
|
36
36
|
TOOL_CALL_OPENERS = ["<tool_call>", "<|tool_call|>", "```tool_call"]
|
|
37
37
|
|
|
38
|
+
THINKING_OPENER = "<think>"
|
|
39
|
+
THINKING_CLOSER = "</think>"
|
|
40
|
+
|
|
38
41
|
TOOL_CALL_PATTERNS = [
|
|
39
42
|
re.compile(r"<tool_call>\s*(\{.*?\})\s*</tool_call>", re.DOTALL),
|
|
40
43
|
re.compile(r"<\|tool_call\|>\s*(\{.*?\})\s*<\|/tool_call\|>", re.DOTALL),
|
|
@@ -51,23 +54,27 @@ def encode_tool_call(name: str, arguments: dict[str, Any]) -> str:
|
|
|
51
54
|
return f'<tool_call>{json.dumps({"name": name, "arguments": arguments})}</tool_call>'
|
|
52
55
|
|
|
53
56
|
|
|
54
|
-
def
|
|
55
|
-
"
|
|
57
|
+
def encode_thinking(text: str) -> str:
|
|
58
|
+
return f"{THINKING_OPENER}{text}{THINKING_CLOSER}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _find_opener(text: str, openers: Sequence[str]) -> int | None:
|
|
62
|
+
"""Find the earliest position of any of `openers` in text, or None."""
|
|
56
63
|
earliest = None
|
|
57
|
-
for opener in
|
|
64
|
+
for opener in openers:
|
|
58
65
|
idx = text.find(opener)
|
|
59
66
|
if idx != -1 and (earliest is None or idx < earliest):
|
|
60
67
|
earliest = idx
|
|
61
68
|
return earliest
|
|
62
69
|
|
|
63
70
|
|
|
64
|
-
def _split_at_potential_prefix(text: str) -> tuple[str, str]:
|
|
71
|
+
def _split_at_potential_prefix(text: str, openers: Sequence[str]) -> tuple[str, str]:
|
|
65
72
|
"""Split text into (safe_to_stream, potential_opener_prefix).
|
|
66
73
|
|
|
67
|
-
The second part is a suffix that could be the beginning of
|
|
68
|
-
|
|
74
|
+
The second part is a suffix that could be the beginning of one of
|
|
75
|
+
`openers`, so it must be held back until more text arrives.
|
|
69
76
|
"""
|
|
70
|
-
for opener in
|
|
77
|
+
for opener in openers:
|
|
71
78
|
for length in range(1, len(opener)):
|
|
72
79
|
if text.endswith(opener[:length]):
|
|
73
80
|
return text[:-length], text[-length:]
|
|
@@ -144,8 +151,10 @@ class ServedCompletingModel(CompletingModel):
|
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
pending = ""
|
|
154
|
+
in_thinking = False
|
|
147
155
|
in_tool_call = False
|
|
148
156
|
tool_call_text = ""
|
|
157
|
+
openers = [THINKING_OPENER, *TOOL_CALL_OPENERS]
|
|
149
158
|
|
|
150
159
|
async with httpx.AsyncClient(timeout=None) as client:
|
|
151
160
|
async with client.stream(
|
|
@@ -162,22 +171,43 @@ class ServedCompletingModel(CompletingModel):
|
|
|
162
171
|
|
|
163
172
|
pending += text
|
|
164
173
|
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
while pending and not in_tool_call:
|
|
175
|
+
if in_thinking:
|
|
176
|
+
closer_pos = pending.find(THINKING_CLOSER)
|
|
177
|
+
if closer_pos == -1:
|
|
178
|
+
thought, pending = _split_at_potential_prefix(
|
|
179
|
+
pending, [THINKING_CLOSER]
|
|
180
|
+
)
|
|
181
|
+
if thought:
|
|
182
|
+
yield CompletionChunk(thinking=thought)
|
|
183
|
+
break
|
|
184
|
+
if closer_pos:
|
|
185
|
+
yield CompletionChunk(thinking=pending[:closer_pos])
|
|
186
|
+
pending = pending[closer_pos + len(THINKING_CLOSER) :]
|
|
187
|
+
in_thinking = False
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
opener_pos = _find_opener(pending, openers)
|
|
191
|
+
if opener_pos is None:
|
|
192
|
+
safe, pending = _split_at_potential_prefix(pending, openers)
|
|
193
|
+
if safe:
|
|
194
|
+
yield CompletionChunk(content=safe)
|
|
195
|
+
break
|
|
196
|
+
|
|
167
197
|
before = pending[:opener_pos]
|
|
168
198
|
if before:
|
|
169
199
|
yield CompletionChunk(content=before)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
if pending.startswith(THINKING_OPENER, opener_pos):
|
|
201
|
+
pending = pending[opener_pos + len(THINKING_OPENER) :]
|
|
202
|
+
in_thinking = True
|
|
203
|
+
else:
|
|
204
|
+
tool_call_text = pending[opener_pos:]
|
|
205
|
+
in_tool_call = True
|
|
206
|
+
pending = ""
|
|
207
|
+
|
|
208
|
+
if pending and in_thinking:
|
|
209
|
+
yield CompletionChunk(thinking=pending)
|
|
210
|
+
elif pending:
|
|
181
211
|
yield CompletionChunk(content=pending)
|
|
182
212
|
|
|
183
213
|
if tool_call_text:
|
|
@@ -28,6 +28,14 @@ from cortexgrid_infer.core import DeployedModel
|
|
|
28
28
|
|
|
29
29
|
_GIB = float(1 << 30)
|
|
30
30
|
|
|
31
|
+
ENABLE_THINKING_PARAM = "enable_thinking"
|
|
32
|
+
|
|
33
|
+
# Whether a replica runs its model through `torch.compile` (see
|
|
34
|
+
# `cortexgrid_infer.compiling`). Off unless the model card says "true": compiled
|
|
35
|
+
# serving pays a warm-up per input shape, which is worth it only where traffic
|
|
36
|
+
# repeats those shapes.
|
|
37
|
+
COMPILE_PARAM = "compile"
|
|
38
|
+
|
|
31
39
|
# Weights are only part of what a replica holds. The multiplier covers CUDA
|
|
32
40
|
# context, activations and fragmentation; the floor covers the KV cache of a
|
|
33
41
|
# long context, which does not scale with model size the way activations do.
|
|
@@ -94,6 +102,10 @@ class LocalModel:
|
|
|
94
102
|
vram_gb=max(round(size * _VRAM_HEADROOM + _VRAM_FLOOR_GB, 1), cls.min_vram_gb),
|
|
95
103
|
)
|
|
96
104
|
|
|
105
|
+
@classmethod
|
|
106
|
+
def config(cls) -> dict[str, str]:
|
|
107
|
+
return {COMPILE_PARAM: "false"}
|
|
108
|
+
|
|
97
109
|
@classmethod
|
|
98
110
|
def client(cls, url: str, name: str) -> DeployedModel:
|
|
99
111
|
"""A client for this app deployed at `url`, serving the model `name`."""
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/base.py
RENAMED
|
@@ -44,6 +44,7 @@ class GeminiModel(HostedModel):
|
|
|
44
44
|
f"{family}/{suffix}/{run_name} is missing {sorted(missing)} from "
|
|
45
45
|
"its config; set them on the model card"
|
|
46
46
|
)
|
|
47
|
+
self._config = config
|
|
47
48
|
self._model = config[MODEL_PARAM]
|
|
48
49
|
self._client = genai.Client(
|
|
49
50
|
api_key=cortexgrid.get_secret(config[API_KEY_SECRET_PARAM])
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/text2text.py
RENAMED
|
@@ -16,8 +16,13 @@ from fastapi import FastAPI
|
|
|
16
16
|
from fastapi.responses import StreamingResponse
|
|
17
17
|
|
|
18
18
|
from cortexgrid_infer.core import Message, ToolSpec
|
|
19
|
-
from cortexgrid_infer.protocols.completion import
|
|
20
|
-
|
|
19
|
+
from cortexgrid_infer.protocols.completion import (
|
|
20
|
+
ServedCompletingModel,
|
|
21
|
+
encode_thinking,
|
|
22
|
+
encode_tool_call,
|
|
23
|
+
)
|
|
24
|
+
from cortexgrid_infer.serve_apps.base import ENABLE_THINKING_PARAM
|
|
25
|
+
from cortexgrid_infer.serve_apps.gemini.base import DEFAULT_API_KEY_SECRET, GeminiModel
|
|
21
26
|
|
|
22
27
|
_app = FastAPI()
|
|
23
28
|
|
|
@@ -109,6 +114,18 @@ def to_gemini_tools(
|
|
|
109
114
|
|
|
110
115
|
@serve.ingress(_app)
|
|
111
116
|
class GeminiText2Text(GeminiModel):
|
|
117
|
+
@classmethod
|
|
118
|
+
def config(
|
|
119
|
+
cls,
|
|
120
|
+
model_id: str,
|
|
121
|
+
api_key_secret: str = DEFAULT_API_KEY_SECRET,
|
|
122
|
+
enable_thinking: str = "true",
|
|
123
|
+
) -> dict[str, str]:
|
|
124
|
+
return {
|
|
125
|
+
**super().config(model_id, api_key_secret),
|
|
126
|
+
ENABLE_THINKING_PARAM: enable_thinking,
|
|
127
|
+
}
|
|
128
|
+
|
|
112
129
|
@classmethod
|
|
113
130
|
def client(cls, url: str, name: str) -> ServedCompletingModel:
|
|
114
131
|
return ServedCompletingModel(url=url, model_id=name)
|
|
@@ -121,6 +138,11 @@ class GeminiText2Text(GeminiModel):
|
|
|
121
138
|
generate_config: dict[str, Any] = {
|
|
122
139
|
"max_output_tokens": body.get("max_new_tokens", 16 * 1024),
|
|
123
140
|
"temperature": body.get("temperature", 0.7),
|
|
141
|
+
"thinking_config": (
|
|
142
|
+
{"include_thoughts": True}
|
|
143
|
+
if self._config.get(ENABLE_THINKING_PARAM, "true") == "true"
|
|
144
|
+
else {"thinking_budget": 0}
|
|
145
|
+
),
|
|
124
146
|
}
|
|
125
147
|
if system_prompt:
|
|
126
148
|
generate_config["system_instruction"] = system_prompt
|
|
@@ -140,7 +162,10 @@ class GeminiText2Text(GeminiModel):
|
|
|
140
162
|
part.function_call.name, dict(part.function_call.args or {})
|
|
141
163
|
).encode("utf-8")
|
|
142
164
|
|
|
143
|
-
elif part.text and
|
|
165
|
+
elif part.text and part.thought:
|
|
166
|
+
yield encode_thinking(part.text).encode("utf-8")
|
|
167
|
+
|
|
168
|
+
elif part.text:
|
|
144
169
|
yield part.text.encode("utf-8")
|
|
145
170
|
|
|
146
171
|
return StreamingResponse(stream(), media_type="text/plain")
|
|
@@ -13,6 +13,7 @@ cortexgrid bundles the subclass's own file, so the model's code travels with it.
|
|
|
13
13
|
min_vram_gb = 6.0
|
|
14
14
|
def load(self, path, device): ...
|
|
15
15
|
def make_mesh(self, image, **options) -> GeneratedMesh: ...
|
|
16
|
+
def compile(self, device): ... # optional; see `Image2Mesh.compile`
|
|
16
17
|
|
|
17
18
|
imp = HuggingFaceImporter("org/my-mesh-model", MyMesh)
|
|
18
19
|
"""
|
|
@@ -31,11 +32,12 @@ from fastapi import FastAPI
|
|
|
31
32
|
from PIL import Image, ImageOps
|
|
32
33
|
import torch
|
|
33
34
|
|
|
35
|
+
from cortexgrid_infer import compiling
|
|
34
36
|
from cortexgrid_infer.core import GeneratedMesh
|
|
35
37
|
from cortexgrid_infer.device import detect_device
|
|
36
38
|
from cortexgrid_infer.protocols import meshing
|
|
37
39
|
from cortexgrid_infer.protocols.meshing import ServedMeshingModel
|
|
38
|
-
from cortexgrid_infer.serve_apps.base import LocalModel
|
|
40
|
+
from cortexgrid_infer.serve_apps.base import COMPILE_PARAM, LocalModel
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
_app = FastAPI()
|
|
@@ -55,11 +57,26 @@ class Image2Mesh(LocalModel):
|
|
|
55
57
|
def __init__(self, family: str, suffix: str, run_name: str) -> None:
|
|
56
58
|
self.device = detect_device()
|
|
57
59
|
self.load(Path(cortexgrid.load_model(family, suffix, run_name)), self.device)
|
|
60
|
+
settings = cortexgrid.model_config(family, suffix, run_name)
|
|
61
|
+
requested = settings.get(COMPILE_PARAM, "false") == "true"
|
|
62
|
+
self.compiled = requested and compiling.supported(self.device)
|
|
63
|
+
if self.compiled:
|
|
64
|
+
self.compile(self.device)
|
|
58
65
|
|
|
59
66
|
def load(self, path: Path, device: torch.device) -> None:
|
|
60
67
|
"""Build the model from its files at `path`, on `device`."""
|
|
61
68
|
raise NotImplementedError
|
|
62
69
|
|
|
70
|
+
def compile(self, device: torch.device) -> None:
|
|
71
|
+
"""Compile what `load` built, on `device`.
|
|
72
|
+
|
|
73
|
+
Called after `load`, and only when the model card asks for it on a
|
|
74
|
+
device worth compiling on. Does nothing unless overridden: which of a
|
|
75
|
+
model's modules pay back compiling, and in which `compiling.Mode`, is
|
|
76
|
+
the model's to know - a denoising loop of fixed shapes suits
|
|
77
|
+
`Mode.GRAPHED`, a decoder queried over a grid whose size moves suits
|
|
78
|
+
`Mode.FUSED`. `compiling.compile_module` does the rest."""
|
|
79
|
+
|
|
63
80
|
def make_mesh(self, image: Image.Image, **options: Any) -> GeneratedMesh:
|
|
64
81
|
"""The mesh of the object in `image`, in the frame `GeneratedMesh`
|
|
65
82
|
documents, with the options the request carried; `params` says what the
|
|
@@ -29,7 +29,7 @@ import torch
|
|
|
29
29
|
from cortexgrid_infer import compiling
|
|
30
30
|
from cortexgrid_infer.device import detect_device
|
|
31
31
|
from cortexgrid_infer.protocols.imaging import ServedGeneratingModel
|
|
32
|
-
from cortexgrid_infer.serve_apps.base import LocalModel
|
|
32
|
+
from cortexgrid_infer.serve_apps.base import COMPILE_PARAM, LocalModel
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
_app = FastAPI()
|
|
@@ -95,8 +95,12 @@ class Text2Image(LocalModel):
|
|
|
95
95
|
vae.enable_tiling()
|
|
96
96
|
self._pipe = pipe
|
|
97
97
|
# A denoising schedule is a loop of identically shaped forwards: capture once, replay.
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
settings = cortexgrid.model_config(family, suffix, run_name)
|
|
99
|
+
requested = settings.get(COMPILE_PARAM, "false") == "true"
|
|
100
|
+
self._compiled = (
|
|
101
|
+
compiling.compile_pipeline(pipe, self._device, compiling.Mode.GRAPHED)
|
|
102
|
+
if requested
|
|
103
|
+
else []
|
|
100
104
|
)
|
|
101
105
|
|
|
102
106
|
@_app.post("/generate")
|
|
@@ -30,7 +30,11 @@ import torch
|
|
|
30
30
|
from cortexgrid_infer import compiling
|
|
31
31
|
from cortexgrid_infer.device import detect_device
|
|
32
32
|
from cortexgrid_infer.protocols.completion import ServedCompletingModel
|
|
33
|
-
from cortexgrid_infer.serve_apps.base import
|
|
33
|
+
from cortexgrid_infer.serve_apps.base import (
|
|
34
|
+
COMPILE_PARAM,
|
|
35
|
+
ENABLE_THINKING_PARAM,
|
|
36
|
+
LocalModel,
|
|
37
|
+
)
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
log = logging.getLogger(__name__)
|
|
@@ -60,6 +64,10 @@ _RESERVED_BODY_KEYS = {
|
|
|
60
64
|
|
|
61
65
|
@serve.ingress(_app)
|
|
62
66
|
class Text2Text(LocalModel):
|
|
67
|
+
@classmethod
|
|
68
|
+
def config(cls) -> dict[str, str]:
|
|
69
|
+
return {**super().config(), ENABLE_THINKING_PARAM: "true"}
|
|
70
|
+
|
|
63
71
|
@classmethod
|
|
64
72
|
def client(cls, url: str, name: str) -> ServedCompletingModel:
|
|
65
73
|
return ServedCompletingModel(url=url, model_id=name)
|
|
@@ -81,11 +89,13 @@ class Text2Text(LocalModel):
|
|
|
81
89
|
self._max_total_tokens = min(
|
|
82
90
|
int(settings.get(MAX_TOTAL_TOKENS_PARAM, DEFAULT_MAX_TOTAL_TOKENS)), context
|
|
83
91
|
)
|
|
92
|
+
self._enable_thinking = settings.get(ENABLE_THINKING_PARAM, "true") == "true"
|
|
84
93
|
# A static cache of fixed length is what makes decode compilable: it
|
|
85
94
|
# pins the shape every forward sees, so the loop is captured once
|
|
86
95
|
# instead of recompiled per token. transformers then compiles `generate`
|
|
87
96
|
# itself, which is why the module is not compiled here as well.
|
|
88
|
-
|
|
97
|
+
requested = settings.get(COMPILE_PARAM, "false") == "true"
|
|
98
|
+
self._compiled = requested and compiling.supported(self._device)
|
|
89
99
|
if self._compiled:
|
|
90
100
|
config = self._model.generation_config
|
|
91
101
|
config.cache_implementation = "static"
|
|
@@ -133,6 +143,7 @@ class Text2Text(LocalModel):
|
|
|
133
143
|
tools=tools,
|
|
134
144
|
tokenize=False,
|
|
135
145
|
add_generation_prompt=True,
|
|
146
|
+
enable_thinking=self._enable_thinking,
|
|
136
147
|
)
|
|
137
148
|
inputs = self._truncate(
|
|
138
149
|
self._tokenizer(prompt, return_tensors="pt"),
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""The text-rewriting serve app: an encoder-decoder LM loaded from the cortexgrid
|
|
2
|
+
registry, answering `POST /rewrite` (see `cortexgrid_infer.protocols.rewriting`).
|
|
3
|
+
|
|
4
|
+
Loads with `AutoModelForSeq2SeqLM`, so it runs any encoder-decoder model (T5,
|
|
5
|
+
BART, Marian, ...) whose weights are in the transformers layout, whichever
|
|
6
|
+
importer staged them. Any task prefix the model expects (`gec: ` for grammar
|
|
7
|
+
correction, say) is the caller's to put in the text.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import cortexgrid
|
|
16
|
+
from cortexgrid import serve
|
|
17
|
+
from fastapi import FastAPI
|
|
18
|
+
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, CompileConfig
|
|
19
|
+
import torch
|
|
20
|
+
|
|
21
|
+
from cortexgrid_infer import compiling
|
|
22
|
+
from cortexgrid_infer.device import detect_device
|
|
23
|
+
from cortexgrid_infer.protocols.rewriting import ServedRewritingModel
|
|
24
|
+
from cortexgrid_infer.serve_apps.base import COMPILE_PARAM, LocalModel
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
log = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_app = FastAPI()
|
|
30
|
+
|
|
31
|
+
# Output budget for a request that names none: T5's training context, and
|
|
32
|
+
# generation stops at the end-of-sequence token well before it for most text.
|
|
33
|
+
DEFAULT_MAX_NEW_TOKENS = 512
|
|
34
|
+
|
|
35
|
+
# Lengths a compiled replica pads a text up to. `generate` sizes the
|
|
36
|
+
# cross-attention cache to the encoded text, so every distinct length would
|
|
37
|
+
# capture the decode loop afresh; rounding up to a power of two bounds that at
|
|
38
|
+
# one capture per bucket, at the cost of a little masked-out padding.
|
|
39
|
+
_INPUT_BUCKETS = (32, 64, 128, 256, 512)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _bucket(length: int) -> int:
|
|
43
|
+
"""The smallest of `_INPUT_BUCKETS` that holds `length` tokens, else the largest."""
|
|
44
|
+
return next((b for b in _INPUT_BUCKETS if b >= length), _INPUT_BUCKETS[-1])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@serve.ingress(_app)
|
|
48
|
+
class TextRewriter(LocalModel):
|
|
49
|
+
@classmethod
|
|
50
|
+
def client(cls, url: str, name: str) -> ServedRewritingModel:
|
|
51
|
+
return ServedRewritingModel(url=url, model_id=name)
|
|
52
|
+
|
|
53
|
+
def __init__(self, family: str, suffix: str, run_name: str) -> None:
|
|
54
|
+
path = cortexgrid.load_model(family, suffix, run_name)
|
|
55
|
+
self._device = detect_device()
|
|
56
|
+
self._tokenizer = AutoTokenizer.from_pretrained(path)
|
|
57
|
+
self._model = AutoModelForSeq2SeqLM.from_pretrained(
|
|
58
|
+
str(path), torch_dtype=torch.bfloat16
|
|
59
|
+
)
|
|
60
|
+
self._model.to(self._device)
|
|
61
|
+
settings = cortexgrid.model_config(family, suffix, run_name)
|
|
62
|
+
# As in Text2Text: a static cache pins the decoder's self-attention to
|
|
63
|
+
# one shape, and transformers compiles the decode step itself. The
|
|
64
|
+
# cross-attention cache is pinned by padding the text; see `_tokenize`.
|
|
65
|
+
requested = settings.get(COMPILE_PARAM, "false") == "true"
|
|
66
|
+
self._compiled = requested and compiling.supported(self._device)
|
|
67
|
+
if self._compiled:
|
|
68
|
+
config = self._model.generation_config
|
|
69
|
+
config.cache_implementation = "static"
|
|
70
|
+
config.max_cache_len = DEFAULT_MAX_NEW_TOKENS
|
|
71
|
+
config.compile_config = CompileConfig(mode=compiling.Mode.GRAPHED)
|
|
72
|
+
|
|
73
|
+
def _tokenize(self, text: str) -> Any:
|
|
74
|
+
"""The model's inputs for `text`, on the device.
|
|
75
|
+
|
|
76
|
+
Compiled, the text is padded up to its bucket, and one longer than the
|
|
77
|
+
last bucket is cut to it, keeping its start."""
|
|
78
|
+
if not self._compiled:
|
|
79
|
+
return self._tokenizer(text, return_tensors="pt").to(self._device)
|
|
80
|
+
length = len(self._tokenizer(text)["input_ids"])
|
|
81
|
+
bucket = _bucket(length)
|
|
82
|
+
if length > bucket:
|
|
83
|
+
log.warning(
|
|
84
|
+
"truncating text from %d to %d tokens, the most this replica takes",
|
|
85
|
+
length, bucket,
|
|
86
|
+
)
|
|
87
|
+
return self._tokenizer(
|
|
88
|
+
text,
|
|
89
|
+
padding="max_length",
|
|
90
|
+
truncation=True,
|
|
91
|
+
max_length=bucket,
|
|
92
|
+
return_tensors="pt",
|
|
93
|
+
).to(self._device)
|
|
94
|
+
|
|
95
|
+
@_app.post("/rewrite")
|
|
96
|
+
async def rewrite(self, body: dict[str, Any]) -> dict[str, str]:
|
|
97
|
+
generation_options = {"max_new_tokens": DEFAULT_MAX_NEW_TOKENS} | {
|
|
98
|
+
key: value for key, value in body.items() if key != "text"
|
|
99
|
+
}
|
|
100
|
+
if self._compiled:
|
|
101
|
+
# A reply longer than the cache would reallocate it and recompile.
|
|
102
|
+
generation_options["max_new_tokens"] = min(
|
|
103
|
+
generation_options["max_new_tokens"], DEFAULT_MAX_NEW_TOKENS
|
|
104
|
+
)
|
|
105
|
+
inputs = self._tokenize(body["text"])
|
|
106
|
+
output = self._model.generate(**inputs, **generation_options)
|
|
107
|
+
return {"text": self._tokenizer.decode(output[0], skip_special_tokens=True)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cortexgrid-infer"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.5"
|
|
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"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""The text-rewriting serve app: an encoder-decoder LM loaded from the cortexgrid
|
|
2
|
-
registry, answering `POST /rewrite` (see `cortexgrid_infer.protocols.rewriting`).
|
|
3
|
-
|
|
4
|
-
Loads with `AutoModelForSeq2SeqLM`, so it runs any encoder-decoder model (T5,
|
|
5
|
-
BART, Marian, ...) whose weights are in the transformers layout, whichever
|
|
6
|
-
importer staged them. Any task prefix the model expects (`gec: ` for grammar
|
|
7
|
-
correction, say) is the caller's to put in the text.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
import cortexgrid
|
|
15
|
-
from cortexgrid import serve
|
|
16
|
-
from fastapi import FastAPI
|
|
17
|
-
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
|
|
18
|
-
import torch
|
|
19
|
-
|
|
20
|
-
from cortexgrid_infer.device import detect_device
|
|
21
|
-
from cortexgrid_infer.protocols.rewriting import ServedRewritingModel
|
|
22
|
-
from cortexgrid_infer.serve_apps.base import LocalModel
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
_app = FastAPI()
|
|
26
|
-
|
|
27
|
-
# Output budget for a request that names none: T5's training context, and
|
|
28
|
-
# generation stops at the end-of-sequence token well before it for most text.
|
|
29
|
-
DEFAULT_MAX_NEW_TOKENS = 512
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@serve.ingress(_app)
|
|
33
|
-
class TextRewriter(LocalModel):
|
|
34
|
-
@classmethod
|
|
35
|
-
def client(cls, url: str, name: str) -> ServedRewritingModel:
|
|
36
|
-
return ServedRewritingModel(url=url, model_id=name)
|
|
37
|
-
|
|
38
|
-
def __init__(self, family: str, suffix: str, run_name: str) -> None:
|
|
39
|
-
path = cortexgrid.load_model(family, suffix, run_name)
|
|
40
|
-
self._device = detect_device()
|
|
41
|
-
self._tokenizer = AutoTokenizer.from_pretrained(path)
|
|
42
|
-
self._model = AutoModelForSeq2SeqLM.from_pretrained(
|
|
43
|
-
str(path), torch_dtype=torch.bfloat16
|
|
44
|
-
)
|
|
45
|
-
self._model.to(self._device)
|
|
46
|
-
|
|
47
|
-
@_app.post("/rewrite")
|
|
48
|
-
async def rewrite(self, body: dict[str, Any]) -> dict[str, str]:
|
|
49
|
-
generation_options = {"max_new_tokens": DEFAULT_MAX_NEW_TOKENS} | {
|
|
50
|
-
key: value for key, value in body.items() if key != "text"
|
|
51
|
-
}
|
|
52
|
-
inputs = self._tokenizer(body["text"], return_tensors="pt").to(self._device)
|
|
53
|
-
output = self._model.generate(**inputs, **generation_options)
|
|
54
|
-
return {"text": self._tokenizer.decode(output[0], skip_special_tokens=True)}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/__init__.py
RENAMED
|
File without changes
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.5}/cortexgrid_infer/serve_apps/gemini/text2image.py
RENAMED
|
File without changes
|
|
File without changes
|