litserve 0.2.6.dev2__tar.gz → 0.2.7.dev0__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.
- {litserve-0.2.6.dev2/src/litserve.egg-info → litserve-0.2.7.dev0}/PKG-INFO +17 -2
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/requirements.txt +1 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/__about__.py +1 -1
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/api.py +18 -0
- litserve-0.2.7.dev0/src/litserve/loops/__init__.py +38 -0
- litserve-0.2.7.dev0/src/litserve/loops/base.py +364 -0
- litserve-0.2.7.dev0/src/litserve/loops/continuous_batching_loop.py +289 -0
- litserve-0.2.7.dev0/src/litserve/loops/loops.py +96 -0
- litserve-0.2.7.dev0/src/litserve/loops/simple_loops.py +252 -0
- litserve-0.2.7.dev0/src/litserve/loops/streaming_loops.py +234 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/server.py +58 -33
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/specs/base.py +4 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/specs/openai.py +13 -8
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/utils.py +22 -4
- litserve-0.2.7.dev0/src/litserve/zmq_queue.py +192 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0/src/litserve.egg-info}/PKG-INFO +17 -2
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve.egg-info/SOURCES.txt +7 -1
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve.egg-info/requires.txt +1 -0
- litserve-0.2.6.dev2/src/litserve/loops.py +0 -983
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/LICENSE +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/MANIFEST.in +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/README.md +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/setup.cfg +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/setup.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/__init__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/__main__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/callbacks/__init__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/callbacks/base.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/callbacks/defaults/__init__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/callbacks/defaults/metric_callback.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/connector.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/docker_builder.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/loggers.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/middlewares.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/python_client.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/schema/__init__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/schema/image.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/specs/__init__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/specs/openai_embedding.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/test_examples/__init__.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/test_examples/openai_embedding_spec_example.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/test_examples/openai_spec_example.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve/test_examples/simple_example.py +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve.egg-info/dependency_links.txt +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve.egg-info/entry_points.txt +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve.egg-info/not-zip-safe +0 -0
- {litserve-0.2.6.dev2 → litserve-0.2.7.dev0}/src/litserve.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: litserve
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7.dev0
|
|
4
4
|
Summary: Lightweight AI server.
|
|
5
5
|
Home-page: https://github.com/Lightning-AI/litserve
|
|
6
6
|
Download-URL: https://github.com/Lightning-AI/litserve
|
|
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
License-File: LICENSE
|
|
30
30
|
Requires-Dist: fastapi>=0.100
|
|
31
31
|
Requires-Dist: uvicorn[standard]>=0.29.0
|
|
32
|
+
Requires-Dist: pyzmq>=22.0.0
|
|
32
33
|
Provides-Extra: perf
|
|
33
34
|
Requires-Dist: jsonargparse; extra == "perf"
|
|
34
35
|
Requires-Dist: tenacity; extra == "perf"
|
|
@@ -51,6 +52,20 @@ Requires-Dist: python-multipart; extra == "test"
|
|
|
51
52
|
Requires-Dist: requests; extra == "test"
|
|
52
53
|
Requires-Dist: torch>2.0.0; extra == "test"
|
|
53
54
|
Requires-Dist: transformers; extra == "test"
|
|
55
|
+
Dynamic: author
|
|
56
|
+
Dynamic: author-email
|
|
57
|
+
Dynamic: classifier
|
|
58
|
+
Dynamic: description
|
|
59
|
+
Dynamic: description-content-type
|
|
60
|
+
Dynamic: download-url
|
|
61
|
+
Dynamic: home-page
|
|
62
|
+
Dynamic: keywords
|
|
63
|
+
Dynamic: license
|
|
64
|
+
Dynamic: project-url
|
|
65
|
+
Dynamic: provides-extra
|
|
66
|
+
Dynamic: requires-dist
|
|
67
|
+
Dynamic: requires-python
|
|
68
|
+
Dynamic: summary
|
|
54
69
|
|
|
55
70
|
<div align='center'>
|
|
56
71
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
__version__ = "0.2.
|
|
14
|
+
__version__ = "0.2.7.dev0"
|
|
15
15
|
__author__ = "Lightning-AI et al."
|
|
16
16
|
__author_email__ = "community@lightning.ai"
|
|
17
17
|
__license__ = "Apache-2.0"
|
|
@@ -137,3 +137,21 @@ class LitAPI(ABC):
|
|
|
137
137
|
)
|
|
138
138
|
return
|
|
139
139
|
self._logger_queue.put((key, value))
|
|
140
|
+
|
|
141
|
+
def has_active_requests(self) -> bool:
|
|
142
|
+
raise NotImplementedError("has_active_requests is not implemented")
|
|
143
|
+
|
|
144
|
+
def has_capacity(self) -> bool:
|
|
145
|
+
raise NotImplementedError("has_capacity is not implemented")
|
|
146
|
+
|
|
147
|
+
def health(self) -> bool:
|
|
148
|
+
"""Check the additional health status of the API.
|
|
149
|
+
|
|
150
|
+
This method is used in the /health endpoint of the server to determine the health status.
|
|
151
|
+
Users can extend this method to include additional health checks specific to their application.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
bool: True if the API is healthy, False otherwise.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
return True
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright The Lightning AI team.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import multiprocessing as mp
|
|
15
|
+
|
|
16
|
+
from litserve.loops.base import LitLoop, _BaseLoop
|
|
17
|
+
from litserve.loops.continuous_batching_loop import ContinuousBatchingLoop, Output
|
|
18
|
+
from litserve.loops.loops import (
|
|
19
|
+
get_default_loop,
|
|
20
|
+
inference_worker,
|
|
21
|
+
)
|
|
22
|
+
from litserve.loops.simple_loops import BatchedLoop, SingleLoop
|
|
23
|
+
from litserve.loops.streaming_loops import BatchedStreamingLoop, StreamingLoop
|
|
24
|
+
|
|
25
|
+
mp.allow_connection_pickling()
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"_BaseLoop",
|
|
29
|
+
"LitLoop",
|
|
30
|
+
"get_default_loop",
|
|
31
|
+
"inference_worker",
|
|
32
|
+
"Output",
|
|
33
|
+
"SingleLoop",
|
|
34
|
+
"BatchedLoop",
|
|
35
|
+
"StreamingLoop",
|
|
36
|
+
"BatchedStreamingLoop",
|
|
37
|
+
"ContinuousBatchingLoop",
|
|
38
|
+
]
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Copyright The Lightning AI team.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import asyncio
|
|
15
|
+
import inspect
|
|
16
|
+
import logging
|
|
17
|
+
import pickle
|
|
18
|
+
import signal
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from abc import ABC
|
|
22
|
+
from queue import Empty, Queue
|
|
23
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
24
|
+
|
|
25
|
+
from starlette.formparsers import MultiPartParser
|
|
26
|
+
|
|
27
|
+
from litserve import LitAPI
|
|
28
|
+
from litserve.callbacks import CallbackRunner
|
|
29
|
+
from litserve.specs.base import LitSpec
|
|
30
|
+
from litserve.utils import LitAPIStatus
|
|
31
|
+
from litserve.zmq_queue import Producer
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
# FastAPI writes form files to disk over 1MB by default, which prevents serialization by multiprocessing
|
|
35
|
+
MultiPartParser.max_file_size = sys.maxsize
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _inject_context(context: Union[List[dict], dict], func, *args, **kwargs):
|
|
39
|
+
sig = inspect.signature(func)
|
|
40
|
+
if "context" in sig.parameters:
|
|
41
|
+
return func(*args, **kwargs, context=context)
|
|
42
|
+
return func(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def collate_requests(
|
|
46
|
+
lit_api: LitAPI, request_queue: Queue, max_batch_size: int, batch_timeout: float
|
|
47
|
+
) -> Tuple[List, List]:
|
|
48
|
+
payloads = []
|
|
49
|
+
timed_out_uids = []
|
|
50
|
+
entered_at = time.monotonic()
|
|
51
|
+
end_time = entered_at + batch_timeout
|
|
52
|
+
apply_timeout = lit_api.request_timeout not in (-1, False)
|
|
53
|
+
|
|
54
|
+
if batch_timeout == 0:
|
|
55
|
+
while len(payloads) < max_batch_size:
|
|
56
|
+
try:
|
|
57
|
+
response_queue_id, uid, timestamp, x_enc = request_queue.get_nowait()
|
|
58
|
+
if apply_timeout and time.monotonic() - timestamp > lit_api.request_timeout:
|
|
59
|
+
timed_out_uids.append((response_queue_id, uid))
|
|
60
|
+
else:
|
|
61
|
+
payloads.append((response_queue_id, uid, x_enc))
|
|
62
|
+
except Empty:
|
|
63
|
+
break
|
|
64
|
+
return payloads, timed_out_uids
|
|
65
|
+
|
|
66
|
+
while time.monotonic() < end_time and len(payloads) < max_batch_size:
|
|
67
|
+
remaining_time = end_time - time.monotonic()
|
|
68
|
+
if remaining_time <= 0:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
response_queue_id, uid, timestamp, x_enc = request_queue.get(timeout=min(remaining_time, 0.001))
|
|
73
|
+
if apply_timeout and time.monotonic() - timestamp > lit_api.request_timeout:
|
|
74
|
+
timed_out_uids.append((response_queue_id, uid))
|
|
75
|
+
else:
|
|
76
|
+
payloads.append((response_queue_id, uid, x_enc))
|
|
77
|
+
|
|
78
|
+
except Empty:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
return payloads, timed_out_uids
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _BaseLoop(ABC):
|
|
85
|
+
"""Loop runs an inference engine that executes a specific set of hooks, implemented in the LitAPI, in a predefined
|
|
86
|
+
order.
|
|
87
|
+
|
|
88
|
+
For a default loop, LitAPI must implement the following hooks:
|
|
89
|
+
- decode_request
|
|
90
|
+
- batch
|
|
91
|
+
- predict
|
|
92
|
+
- unbatch
|
|
93
|
+
- encode_response
|
|
94
|
+
|
|
95
|
+
To implement a custom loop, subclass this class and implement the `run` method. The `run` method should execute the
|
|
96
|
+
hooks in the desired order.
|
|
97
|
+
|
|
98
|
+
`__call__` method is the entry point for the worker process. It calls the `run` method in a loop until the worker is
|
|
99
|
+
terminated.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
class TestLoop(_BaseLoop):
|
|
105
|
+
def run(
|
|
106
|
+
self,
|
|
107
|
+
lit_api: LitAPI,
|
|
108
|
+
lit_spec: Optional[LitSpec],
|
|
109
|
+
device: str,
|
|
110
|
+
worker_id: int,
|
|
111
|
+
request_queue: Queue,
|
|
112
|
+
response_queues: List[Queue],
|
|
113
|
+
max_batch_size: int,
|
|
114
|
+
batch_timeout: float,
|
|
115
|
+
stream: bool,
|
|
116
|
+
workers_setup_status: Dict[int, str],
|
|
117
|
+
callback_runner: CallbackRunner,
|
|
118
|
+
):
|
|
119
|
+
item = request_queue.get()
|
|
120
|
+
if item is None:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
response_queue_id, uid, timestamp, x_enc = item
|
|
124
|
+
# Expects LitAPI to implement the load_cache method
|
|
125
|
+
lit_api.load_cache(x_enc)
|
|
126
|
+
x = lit_api.decode_request(x_enc)
|
|
127
|
+
response = lit_api.predict(x)
|
|
128
|
+
response_enc = lit_api.encode_response(response)
|
|
129
|
+
response_queues[response_queue_id].put((uid, (response_enc, LitAPIStatus.OK)))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def pre_setup(self, lit_api: LitAPI, spec: Optional[LitSpec]):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
async def schedule_task(
|
|
138
|
+
self,
|
|
139
|
+
lit_api: LitAPI,
|
|
140
|
+
lit_spec: Optional[LitSpec],
|
|
141
|
+
request_queue: Queue,
|
|
142
|
+
max_batch_size: int,
|
|
143
|
+
batch_timeout: float,
|
|
144
|
+
response_queues: List[Queue],
|
|
145
|
+
):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
def __call__(
|
|
149
|
+
self,
|
|
150
|
+
lit_api: LitAPI,
|
|
151
|
+
lit_spec: Optional[LitSpec],
|
|
152
|
+
device: str,
|
|
153
|
+
worker_id: int,
|
|
154
|
+
request_queue: Queue,
|
|
155
|
+
response_queues: List[Queue],
|
|
156
|
+
max_batch_size: int,
|
|
157
|
+
batch_timeout: float,
|
|
158
|
+
stream: bool,
|
|
159
|
+
workers_setup_status: Dict[int, str],
|
|
160
|
+
callback_runner: CallbackRunner,
|
|
161
|
+
):
|
|
162
|
+
if asyncio.iscoroutinefunction(self.run):
|
|
163
|
+
event_loop = asyncio.new_event_loop()
|
|
164
|
+
|
|
165
|
+
async def _wrapper():
|
|
166
|
+
logger.info("Running LitLoop in a asyncio event loop")
|
|
167
|
+
future = self.schedule_task(
|
|
168
|
+
lit_api, lit_spec, request_queue, max_batch_size, batch_timeout, response_queues
|
|
169
|
+
)
|
|
170
|
+
_ = event_loop.create_task(future)
|
|
171
|
+
while True:
|
|
172
|
+
try:
|
|
173
|
+
await self.run(
|
|
174
|
+
lit_api,
|
|
175
|
+
lit_spec,
|
|
176
|
+
device,
|
|
177
|
+
worker_id,
|
|
178
|
+
request_queue,
|
|
179
|
+
response_queues,
|
|
180
|
+
max_batch_size,
|
|
181
|
+
batch_timeout,
|
|
182
|
+
stream,
|
|
183
|
+
workers_setup_status,
|
|
184
|
+
callback_runner,
|
|
185
|
+
)
|
|
186
|
+
await asyncio.sleep(0)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.exception("An error occurred in the loop: %s", e)
|
|
189
|
+
|
|
190
|
+
event_loop.run_until_complete(_wrapper())
|
|
191
|
+
else:
|
|
192
|
+
while True:
|
|
193
|
+
self.run(
|
|
194
|
+
lit_api,
|
|
195
|
+
lit_spec,
|
|
196
|
+
device,
|
|
197
|
+
worker_id,
|
|
198
|
+
request_queue,
|
|
199
|
+
response_queues,
|
|
200
|
+
max_batch_size,
|
|
201
|
+
batch_timeout,
|
|
202
|
+
stream,
|
|
203
|
+
workers_setup_status,
|
|
204
|
+
callback_runner,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def run(
|
|
208
|
+
self,
|
|
209
|
+
lit_api: LitAPI,
|
|
210
|
+
lit_spec: Optional[LitSpec],
|
|
211
|
+
device: str,
|
|
212
|
+
worker_id: int,
|
|
213
|
+
request_queue: Queue,
|
|
214
|
+
response_queues: List[Queue],
|
|
215
|
+
max_batch_size: int,
|
|
216
|
+
batch_timeout: float,
|
|
217
|
+
stream: bool,
|
|
218
|
+
workers_setup_status: Dict[int, str],
|
|
219
|
+
callback_runner: CallbackRunner,
|
|
220
|
+
):
|
|
221
|
+
raise NotImplementedError
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class LitLoop(_BaseLoop):
|
|
225
|
+
def __init__(self):
|
|
226
|
+
self.producer: Optional[Producer] = None
|
|
227
|
+
self._context = {}
|
|
228
|
+
self._setup_signal_handlers()
|
|
229
|
+
|
|
230
|
+
def get_batch_requests(self, lit_api: LitAPI, request_queue: Queue, max_batch_size: int, batch_timeout: float):
|
|
231
|
+
batches, timed_out_uids = collate_requests(
|
|
232
|
+
lit_api,
|
|
233
|
+
request_queue,
|
|
234
|
+
max_batch_size,
|
|
235
|
+
batch_timeout,
|
|
236
|
+
)
|
|
237
|
+
return batches, timed_out_uids
|
|
238
|
+
|
|
239
|
+
def get_request(self, request_queue: Queue, block: bool = True, timeout: Optional[float] = None):
|
|
240
|
+
try:
|
|
241
|
+
return request_queue.get(block=block, timeout=timeout)
|
|
242
|
+
except Empty:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def populate_context(self, lit_spec: LitSpec, request: Any):
|
|
246
|
+
if lit_spec and hasattr(lit_spec, "populate_context"):
|
|
247
|
+
lit_spec.populate_context(self._context, request)
|
|
248
|
+
|
|
249
|
+
def put_response(
|
|
250
|
+
self, response_queues: List[Queue], response_queue_id: int, uid: str, response_data: Any, status: LitAPIStatus
|
|
251
|
+
) -> None:
|
|
252
|
+
if self.producer:
|
|
253
|
+
self.producer.put((uid, (response_data, status)), consumer_id=response_queue_id)
|
|
254
|
+
else:
|
|
255
|
+
response_queues[response_queue_id].put((uid, (response_data, status)), block=False)
|
|
256
|
+
|
|
257
|
+
def put_error_response(
|
|
258
|
+
self, response_queues: List[Queue], response_queue_id: int, uid: str, error: Exception
|
|
259
|
+
) -> None:
|
|
260
|
+
error = pickle.dumps(error)
|
|
261
|
+
self.put_response(response_queues, response_queue_id, uid, error, LitAPIStatus.ERROR)
|
|
262
|
+
|
|
263
|
+
def __del__(self):
|
|
264
|
+
if self.producer:
|
|
265
|
+
self.producer.close()
|
|
266
|
+
|
|
267
|
+
def _setup_signal_handlers(self):
|
|
268
|
+
def cleanup_handler(signum=None, frame=None):
|
|
269
|
+
logging.debug("Worker process received shutdown signal")
|
|
270
|
+
if self.producer:
|
|
271
|
+
self.producer.close()
|
|
272
|
+
sys.exit(0)
|
|
273
|
+
|
|
274
|
+
signal.signal(signal.SIGINT, cleanup_handler)
|
|
275
|
+
signal.signal(signal.SIGTERM, cleanup_handler)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class DefaultLoop(LitLoop):
|
|
279
|
+
def pre_setup(self, lit_api: LitAPI, spec: Optional[LitSpec]):
|
|
280
|
+
# we will sanitize regularly if no spec
|
|
281
|
+
# in case, we have spec then:
|
|
282
|
+
# case 1: spec implements a streaming API
|
|
283
|
+
# Case 2: spec implements a non-streaming API
|
|
284
|
+
if spec:
|
|
285
|
+
# TODO: Implement sanitization
|
|
286
|
+
lit_api._spec = spec
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
original = lit_api.unbatch.__code__ is LitAPI.unbatch.__code__
|
|
290
|
+
if not lit_api.stream and any([
|
|
291
|
+
inspect.isgeneratorfunction(lit_api.predict),
|
|
292
|
+
inspect.isgeneratorfunction(lit_api.encode_response),
|
|
293
|
+
]):
|
|
294
|
+
raise ValueError(
|
|
295
|
+
"""When `stream=False`, `lit_api.predict`, `lit_api.encode_response` must not be
|
|
296
|
+
generator functions.
|
|
297
|
+
|
|
298
|
+
Correct usage:
|
|
299
|
+
|
|
300
|
+
def predict(self, inputs):
|
|
301
|
+
...
|
|
302
|
+
return {"output": output}
|
|
303
|
+
|
|
304
|
+
Incorrect usage:
|
|
305
|
+
|
|
306
|
+
def predict(self, inputs):
|
|
307
|
+
...
|
|
308
|
+
for i in range(max_token_length):
|
|
309
|
+
yield prediction
|
|
310
|
+
"""
|
|
311
|
+
)
|
|
312
|
+
if (
|
|
313
|
+
lit_api.stream
|
|
314
|
+
and lit_api.max_batch_size > 1
|
|
315
|
+
and not all([
|
|
316
|
+
inspect.isgeneratorfunction(lit_api.predict),
|
|
317
|
+
inspect.isgeneratorfunction(lit_api.encode_response),
|
|
318
|
+
(original or inspect.isgeneratorfunction(lit_api.unbatch)),
|
|
319
|
+
])
|
|
320
|
+
):
|
|
321
|
+
raise ValueError(
|
|
322
|
+
"""When `stream=True` with max_batch_size > 1, `lit_api.predict`, `lit_api.encode_response` and
|
|
323
|
+
`lit_api.unbatch` must generate values using `yield`.
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
|
|
327
|
+
def predict(self, inputs):
|
|
328
|
+
...
|
|
329
|
+
for i in range(max_token_length):
|
|
330
|
+
yield prediction
|
|
331
|
+
|
|
332
|
+
def encode_response(self, outputs):
|
|
333
|
+
for output in outputs:
|
|
334
|
+
encoded_output = ...
|
|
335
|
+
yield encoded_output
|
|
336
|
+
|
|
337
|
+
def unbatch(self, outputs):
|
|
338
|
+
for output in outputs:
|
|
339
|
+
unbatched_output = ...
|
|
340
|
+
yield unbatched_output
|
|
341
|
+
"""
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if lit_api.stream and not all([
|
|
345
|
+
inspect.isgeneratorfunction(lit_api.predict),
|
|
346
|
+
inspect.isgeneratorfunction(lit_api.encode_response),
|
|
347
|
+
]):
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"""When `stream=True` both `lit_api.predict` and
|
|
350
|
+
`lit_api.encode_response` must generate values using `yield`.
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
|
|
354
|
+
def predict(self, inputs):
|
|
355
|
+
...
|
|
356
|
+
for i in range(max_token_length):
|
|
357
|
+
yield prediction
|
|
358
|
+
|
|
359
|
+
def encode_response(self, outputs):
|
|
360
|
+
for output in outputs:
|
|
361
|
+
encoded_output = ...
|
|
362
|
+
yield encoded_output
|
|
363
|
+
"""
|
|
364
|
+
)
|