cortexgrid-infer 0.3.3__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/PKG-INFO +2 -2
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/README.md +1 -1
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/core.py +1 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/importers/base.py +3 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/protocols/completion.py +50 -20
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/base.py +6 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/gemini/base.py +1 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/gemini/text2text.py +28 -3
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/text2text.py +7 -1
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/pyproject.toml +1 -1
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/.gitignore +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/LICENSE +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/compiling.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/device.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/importers/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/importers/huggingface.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/protocols/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/protocols/imaging.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/protocols/meshing.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/protocols/rewriting.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/py.typed +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/registry.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/anthropic.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/gemini/__init__.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/gemini/text2image.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/image2mesh.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/text2image.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/text_rewriter.py +0 -0
- {cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/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.4
|
|
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
|
|
@@ -245,7 +245,7 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
|
|
|
245
245
|
|
|
246
246
|
## Inference
|
|
247
247
|
|
|
248
|
-
`complete` streams `CompletionChunk`s (`.content`, `.tool_calls`, `.finish_reason`):
|
|
248
|
+
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`):
|
|
249
249
|
|
|
250
250
|
```python
|
|
251
251
|
imp = mg.HuggingFaceImporter("Qwen/Qwen2.5-7B-Instruct", mg.Text2Text)
|
|
@@ -217,7 +217,7 @@ it is the floor; only quantizing the weights or running fewer forwards moves it.
|
|
|
217
217
|
|
|
218
218
|
## Inference
|
|
219
219
|
|
|
220
|
-
`complete` streams `CompletionChunk`s (`.content`, `.tool_calls`, `.finish_reason`):
|
|
220
|
+
`complete` streams `CompletionChunk`s (`.content`, `.thinking`, `.tool_calls`, `.finish_reason`):
|
|
221
221
|
|
|
222
222
|
```python
|
|
223
223
|
imp = mg.HuggingFaceImporter("Qwen/Qwen2.5-7B-Instruct", mg.Text2Text)
|
|
@@ -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,8 @@ from cortexgrid_infer.core import DeployedModel
|
|
|
28
28
|
|
|
29
29
|
_GIB = float(1 << 30)
|
|
30
30
|
|
|
31
|
+
ENABLE_THINKING_PARAM = "enable_thinking"
|
|
32
|
+
|
|
31
33
|
# Weights are only part of what a replica holds. The multiplier covers CUDA
|
|
32
34
|
# context, activations and fragmentation; the floor covers the KV cache of a
|
|
33
35
|
# long context, which does not scale with model size the way activations do.
|
|
@@ -94,6 +96,10 @@ class LocalModel:
|
|
|
94
96
|
vram_gb=max(round(size * _VRAM_HEADROOM + _VRAM_FLOOR_GB, 1), cls.min_vram_gb),
|
|
95
97
|
)
|
|
96
98
|
|
|
99
|
+
@classmethod
|
|
100
|
+
def config(cls) -> dict[str, str]:
|
|
101
|
+
return {}
|
|
102
|
+
|
|
97
103
|
@classmethod
|
|
98
104
|
def client(cls, url: str, name: str) -> DeployedModel:
|
|
99
105
|
"""A client for this app deployed at `url`, serving the model `name`."""
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/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.4}/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")
|
|
@@ -30,7 +30,7 @@ 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 LocalModel
|
|
33
|
+
from cortexgrid_infer.serve_apps.base import ENABLE_THINKING_PARAM, LocalModel
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
log = logging.getLogger(__name__)
|
|
@@ -60,6 +60,10 @@ _RESERVED_BODY_KEYS = {
|
|
|
60
60
|
|
|
61
61
|
@serve.ingress(_app)
|
|
62
62
|
class Text2Text(LocalModel):
|
|
63
|
+
@classmethod
|
|
64
|
+
def config(cls) -> dict[str, str]:
|
|
65
|
+
return {ENABLE_THINKING_PARAM: "true"}
|
|
66
|
+
|
|
63
67
|
@classmethod
|
|
64
68
|
def client(cls, url: str, name: str) -> ServedCompletingModel:
|
|
65
69
|
return ServedCompletingModel(url=url, model_id=name)
|
|
@@ -81,6 +85,7 @@ class Text2Text(LocalModel):
|
|
|
81
85
|
self._max_total_tokens = min(
|
|
82
86
|
int(settings.get(MAX_TOTAL_TOKENS_PARAM, DEFAULT_MAX_TOTAL_TOKENS)), context
|
|
83
87
|
)
|
|
88
|
+
self._enable_thinking = settings.get(ENABLE_THINKING_PARAM, "true") == "true"
|
|
84
89
|
# A static cache of fixed length is what makes decode compilable: it
|
|
85
90
|
# pins the shape every forward sees, so the loop is captured once
|
|
86
91
|
# instead of recompiled per token. transformers then compiles `generate`
|
|
@@ -133,6 +138,7 @@ class Text2Text(LocalModel):
|
|
|
133
138
|
tools=tools,
|
|
134
139
|
tokenize=False,
|
|
135
140
|
add_generation_prompt=True,
|
|
141
|
+
enable_thinking=self._enable_thinking,
|
|
136
142
|
)
|
|
137
143
|
inputs = self._truncate(
|
|
138
144
|
self._tokenizer(prompt, return_tensors="pt"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cortexgrid-infer"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
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"
|
|
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.4}/cortexgrid_infer/serve_apps/gemini/__init__.py
RENAMED
|
File without changes
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/gemini/text2image.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cortexgrid_infer-0.3.3 → cortexgrid_infer-0.3.4}/cortexgrid_infer/serve_apps/text_rewriter.py
RENAMED
|
File without changes
|
|
File without changes
|