cortexgrid-infer 0.2.3__tar.gz → 0.2.6__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.2.3 → cortexgrid_infer-0.2.6}/PKG-INFO +26 -11
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/README.md +25 -10
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/huggingface_complete_serve.py +64 -6
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/pyproject.toml +1 -1
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/.gitignore +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/LICENSE +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/__init__.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/compiling.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/completion.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/core.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/device.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/importing.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/__init__.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/anthropic.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/anthropic_serve.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/huggingface_complete.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/huggingface_image.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/huggingface_image_serve.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/py.typed +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/requirements.py +0 -0
- {cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cortexgrid-infer
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Deploy and interact with models across providers (Anthropic, HuggingFace LLM + diffusion)
|
|
5
5
|
Project-URL: Homepage, https://github.com/robodatalab/cortexgrid-infer
|
|
6
6
|
Project-URL: Repository, https://github.com/robodatalab/cortexgrid-infer
|
|
@@ -178,16 +178,31 @@ Both serve apps are tuned rather than defaulted:
|
|
|
178
178
|
whole loop. The VAE is left alone: smallest win of the three, and the one whose
|
|
179
179
|
shapes move most, especially with tiling on.
|
|
180
180
|
- **`HuggingFaceCompletingDeployment`** does *not* compile the module. Decode has
|
|
181
|
-
no stable shape
|
|
182
|
-
`cache_implementation="static"`
|
|
183
|
-
transformers compiles `generate`
|
|
184
|
-
separately, which it is better placed to
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
no stable shape of its own — the sequence grows a token per forward — so it
|
|
182
|
+
pins one: `cache_implementation="static"` with a fixed `max_cache_len`, plus a
|
|
183
|
+
`CompileConfig(mode=Mode.GRAPHED)`. transformers then compiles `generate`
|
|
184
|
+
itself, capturing prefill and decode separately, which it is better placed to
|
|
185
|
+
do; compiling the module here as well would compile the same forward twice.
|
|
186
|
+
|
|
187
|
+
The fixed length is the point, and how long it is decides what decode costs:
|
|
188
|
+
every token's attention reads the whole cache, filled or not. That is why it is
|
|
189
|
+
*not* pinned to the model's context — 32k slots of a Qwen2.5-3B KV cache is
|
|
190
|
+
1.2 GB of keys and values re-read per generated token, several times the
|
|
191
|
+
traffic of the model's own weights, to make room for a prompt nobody sent. It
|
|
192
|
+
is pinned instead to the prompt and reply a caller plausibly sends: 4096 tokens,
|
|
193
|
+
or `max_total_tokens` on the model card, never past the model's own
|
|
194
|
+
`max_position_embeddings`. A reply takes at most half of it, so an
|
|
195
|
+
over-optimistic `max_new_tokens` cannot squeeze the prompt to nothing, and a
|
|
196
|
+
prompt that will not fit in the rest is truncated — keeping the end, since a
|
|
197
|
+
chat template puts the turn to answer last — with a warning naming both
|
|
198
|
+
lengths. Serving it whole would resize the cache and recompile the decode loop,
|
|
199
|
+
which costs more than the generation itself.
|
|
200
|
+
|
|
201
|
+
The image app asks to be configured for nothing, for the same reason neither asks
|
|
202
|
+
which dtype to load in; the completion app takes `max_total_tokens` alone, because
|
|
203
|
+
how much cache a caller needs is the one thing the model cannot tell it. Both
|
|
204
|
+
happen on CUDA and nowhere else — inductor is weakest off it, and a host
|
|
205
|
+
outrunning its accelerator is a GPU problem to begin with.
|
|
191
206
|
|
|
192
207
|
Two things worth knowing:
|
|
193
208
|
|
|
@@ -152,16 +152,31 @@ Both serve apps are tuned rather than defaulted:
|
|
|
152
152
|
whole loop. The VAE is left alone: smallest win of the three, and the one whose
|
|
153
153
|
shapes move most, especially with tiling on.
|
|
154
154
|
- **`HuggingFaceCompletingDeployment`** does *not* compile the module. Decode has
|
|
155
|
-
no stable shape
|
|
156
|
-
`cache_implementation="static"`
|
|
157
|
-
transformers compiles `generate`
|
|
158
|
-
separately, which it is better placed to
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
155
|
+
no stable shape of its own — the sequence grows a token per forward — so it
|
|
156
|
+
pins one: `cache_implementation="static"` with a fixed `max_cache_len`, plus a
|
|
157
|
+
`CompileConfig(mode=Mode.GRAPHED)`. transformers then compiles `generate`
|
|
158
|
+
itself, capturing prefill and decode separately, which it is better placed to
|
|
159
|
+
do; compiling the module here as well would compile the same forward twice.
|
|
160
|
+
|
|
161
|
+
The fixed length is the point, and how long it is decides what decode costs:
|
|
162
|
+
every token's attention reads the whole cache, filled or not. That is why it is
|
|
163
|
+
*not* pinned to the model's context — 32k slots of a Qwen2.5-3B KV cache is
|
|
164
|
+
1.2 GB of keys and values re-read per generated token, several times the
|
|
165
|
+
traffic of the model's own weights, to make room for a prompt nobody sent. It
|
|
166
|
+
is pinned instead to the prompt and reply a caller plausibly sends: 4096 tokens,
|
|
167
|
+
or `max_total_tokens` on the model card, never past the model's own
|
|
168
|
+
`max_position_embeddings`. A reply takes at most half of it, so an
|
|
169
|
+
over-optimistic `max_new_tokens` cannot squeeze the prompt to nothing, and a
|
|
170
|
+
prompt that will not fit in the rest is truncated — keeping the end, since a
|
|
171
|
+
chat template puts the turn to answer last — with a warning naming both
|
|
172
|
+
lengths. Serving it whole would resize the cache and recompile the decode loop,
|
|
173
|
+
which costs more than the generation itself.
|
|
174
|
+
|
|
175
|
+
The image app asks to be configured for nothing, for the same reason neither asks
|
|
176
|
+
which dtype to load in; the completion app takes `max_total_tokens` alone, because
|
|
177
|
+
how much cache a caller needs is the one thing the model cannot tell it. Both
|
|
178
|
+
happen on CUDA and nowhere else — inductor is weakest off it, and a host
|
|
179
|
+
outrunning its accelerator is a GPU problem to begin with.
|
|
165
180
|
|
|
166
181
|
Two things worth knowing:
|
|
167
182
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import logging
|
|
6
7
|
from collections.abc import AsyncIterator
|
|
7
8
|
from threading import Thread
|
|
8
9
|
from typing import Any
|
|
@@ -11,16 +12,32 @@ import cortexgrid
|
|
|
11
12
|
from cortexgrid import serve
|
|
12
13
|
from fastapi import FastAPI
|
|
13
14
|
from fastapi.responses import StreamingResponse
|
|
14
|
-
from transformers import
|
|
15
|
+
from transformers import (
|
|
16
|
+
AutoModelForCausalLM,
|
|
17
|
+
AutoTokenizer,
|
|
18
|
+
CompileConfig,
|
|
19
|
+
TextIteratorStreamer,
|
|
20
|
+
)
|
|
15
21
|
import torch
|
|
16
22
|
|
|
17
23
|
from cortexgrid_infer import compiling
|
|
18
24
|
from cortexgrid_infer.device import detect_device
|
|
19
25
|
|
|
20
26
|
|
|
27
|
+
log = logging.getLogger(__name__)
|
|
28
|
+
|
|
21
29
|
_app = FastAPI()
|
|
22
30
|
|
|
23
31
|
|
|
32
|
+
# Prompt + reply a replica is sized for. The static cache is allocated at this
|
|
33
|
+
# many slots and every decoded token reads all of them, filled or not: at
|
|
34
|
+
# Qwen2.5-3B's 32k context that is 1.2 GB of keys and values re-read per token,
|
|
35
|
+
# which costs far more than the prompt it makes room for. So the default is what
|
|
36
|
+
# a caller plausibly sends rather than what the model could hold; a model that
|
|
37
|
+
# needs more takes `max_total_tokens` on its model card, up to its own context.
|
|
38
|
+
MAX_TOTAL_TOKENS_PARAM = "max_total_tokens"
|
|
39
|
+
DEFAULT_MAX_TOTAL_TOKENS = 4096
|
|
40
|
+
|
|
24
41
|
_RESERVED_BODY_KEYS = {
|
|
25
42
|
"messages",
|
|
26
43
|
"tools",
|
|
@@ -44,16 +61,54 @@ class HuggingFaceCompletingDeployment:
|
|
|
44
61
|
str(path), torch_dtype=torch.float16
|
|
45
62
|
)
|
|
46
63
|
self._model.to(self._device) # type: ignore
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self._model
|
|
64
|
+
settings = cortexgrid.model_config(family, suffix, run_name)
|
|
65
|
+
context = getattr(
|
|
66
|
+
self._model.config, "max_position_embeddings", DEFAULT_MAX_TOTAL_TOKENS
|
|
67
|
+
)
|
|
68
|
+
self._max_total_tokens = min(
|
|
69
|
+
int(settings.get(MAX_TOTAL_TOKENS_PARAM, DEFAULT_MAX_TOTAL_TOKENS)), context
|
|
50
70
|
)
|
|
71
|
+
# A static cache of fixed length is what makes decode compilable: it
|
|
72
|
+
# pins the shape every forward sees, so the loop is captured once
|
|
73
|
+
# instead of recompiled per token. transformers then compiles `generate`
|
|
74
|
+
# itself, which is why the module is not compiled here as well.
|
|
75
|
+
self._compiled = compiling.supported(self._device)
|
|
76
|
+
if self._compiled:
|
|
77
|
+
config = self._model.generation_config
|
|
78
|
+
config.cache_implementation = "static"
|
|
79
|
+
config.max_cache_len = self._max_total_tokens
|
|
80
|
+
config.compile_config = CompileConfig(mode=compiling.Mode.GRAPHED)
|
|
81
|
+
|
|
82
|
+
def _room_for_the_reply(self, requested: int) -> int:
|
|
83
|
+
"""How many tokens a reply may take, leaving the prompt the rest.
|
|
84
|
+
|
|
85
|
+
Half the budget at most, so a caller asking for more output than the
|
|
86
|
+
replica is sized for cannot squeeze the prompt down to nothing."""
|
|
87
|
+
return min(requested, self._max_total_tokens // 2)
|
|
88
|
+
|
|
89
|
+
def _truncate(self, inputs: Any, limit: int) -> Any:
|
|
90
|
+
"""Cut an over-long prompt to ``limit`` tokens, keeping its end.
|
|
91
|
+
|
|
92
|
+
A prompt and reply that together outgrew the cache would reallocate it
|
|
93
|
+
and recompile the decode loop, which costs more than the generation."""
|
|
94
|
+
length = inputs["input_ids"].shape[-1]
|
|
95
|
+
if length <= limit:
|
|
96
|
+
return inputs
|
|
97
|
+
log.warning(
|
|
98
|
+
"truncating prompt from %d to %d tokens, the room this replica has",
|
|
99
|
+
length, limit,
|
|
100
|
+
)
|
|
101
|
+
for key, value in inputs.items():
|
|
102
|
+
inputs[key] = value[..., -limit:]
|
|
103
|
+
return inputs
|
|
51
104
|
|
|
52
105
|
@_app.post("/complete")
|
|
53
106
|
async def complete(self, body: dict[str, Any]) -> StreamingResponse:
|
|
54
107
|
messages = body["messages"]
|
|
55
108
|
tools = body.get("tools")
|
|
56
|
-
max_new_tokens =
|
|
109
|
+
max_new_tokens = self._room_for_the_reply(
|
|
110
|
+
body.get("max_new_tokens", self._max_total_tokens)
|
|
111
|
+
)
|
|
57
112
|
temperature = body.get("temperature", 0.7)
|
|
58
113
|
top_p = body.get("top_p", 0.9)
|
|
59
114
|
top_k = body.get("top_k", 50)
|
|
@@ -66,7 +121,10 @@ class HuggingFaceCompletingDeployment:
|
|
|
66
121
|
tokenize=False,
|
|
67
122
|
add_generation_prompt=True,
|
|
68
123
|
)
|
|
69
|
-
inputs = self.
|
|
124
|
+
inputs = self._truncate(
|
|
125
|
+
self._tokenizer(prompt, return_tensors="pt"),
|
|
126
|
+
self._max_total_tokens - max_new_tokens,
|
|
127
|
+
).to(self._model.device)
|
|
70
128
|
|
|
71
129
|
queue: asyncio.Queue[str | None] = asyncio.Queue()
|
|
72
130
|
error: list[BaseException] = []
|
|
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.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/anthropic_serve.py
RENAMED
|
File without changes
|
{cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/huggingface_complete.py
RENAMED
|
File without changes
|
{cortexgrid_infer-0.2.3 → cortexgrid_infer-0.2.6}/cortexgrid_infer/providers/huggingface_image.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|