litserve 0.2.6.dev1__tar.gz → 0.2.6.dev2__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.
Files changed (40) hide show
  1. {litserve-0.2.6.dev1/src/litserve.egg-info → litserve-0.2.6.dev2}/PKG-INFO +5 -5
  2. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/__about__.py +1 -1
  3. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/api.py +2 -2
  4. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/loops.py +72 -28
  5. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/specs/base.py +4 -1
  6. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/specs/openai.py +5 -5
  7. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2/src/litserve.egg-info}/PKG-INFO +5 -5
  8. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/LICENSE +0 -0
  9. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/MANIFEST.in +0 -0
  10. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/README.md +0 -0
  11. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/requirements.txt +0 -0
  12. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/setup.cfg +0 -0
  13. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/setup.py +0 -0
  14. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/__init__.py +0 -0
  15. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/__main__.py +0 -0
  16. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/callbacks/__init__.py +0 -0
  17. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/callbacks/base.py +0 -0
  18. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/callbacks/defaults/__init__.py +0 -0
  19. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/callbacks/defaults/metric_callback.py +0 -0
  20. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/connector.py +0 -0
  21. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/docker_builder.py +0 -0
  22. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/loggers.py +0 -0
  23. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/middlewares.py +0 -0
  24. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/python_client.py +0 -0
  25. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/schema/__init__.py +0 -0
  26. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/schema/image.py +0 -0
  27. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/server.py +0 -0
  28. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/specs/__init__.py +0 -0
  29. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/specs/openai_embedding.py +0 -0
  30. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/test_examples/__init__.py +0 -0
  31. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/test_examples/openai_embedding_spec_example.py +0 -0
  32. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/test_examples/openai_spec_example.py +0 -0
  33. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/test_examples/simple_example.py +0 -0
  34. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve/utils.py +0 -0
  35. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve.egg-info/SOURCES.txt +0 -0
  36. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve.egg-info/dependency_links.txt +0 -0
  37. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve.egg-info/entry_points.txt +0 -0
  38. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve.egg-info/not-zip-safe +0 -0
  39. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve.egg-info/requires.txt +0 -0
  40. {litserve-0.2.6.dev1 → litserve-0.2.6.dev2}/src/litserve.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: litserve
3
- Version: 0.2.6.dev1
3
+ Version: 0.2.6.dev2
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,10 @@ 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
+ Provides-Extra: perf
33
+ Requires-Dist: jsonargparse; extra == "perf"
34
+ Requires-Dist: tenacity; extra == "perf"
35
+ Requires-Dist: uvloop; extra == "perf"
32
36
  Provides-Extra: test
33
37
  Requires-Dist: asgi-lifespan; extra == "test"
34
38
  Requires-Dist: coverage[toml]>=7.5.3; extra == "test"
@@ -47,10 +51,6 @@ Requires-Dist: python-multipart; extra == "test"
47
51
  Requires-Dist: requests; extra == "test"
48
52
  Requires-Dist: torch>2.0.0; extra == "test"
49
53
  Requires-Dist: transformers; extra == "test"
50
- Provides-Extra: perf
51
- Requires-Dist: jsonargparse; extra == "perf"
52
- Requires-Dist: tenacity; extra == "perf"
53
- Requires-Dist: uvloop; extra == "perf"
54
54
 
55
55
  <div align='center'>
56
56
 
@@ -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.6.dev1"
14
+ __version__ = "0.2.6.dev2"
15
15
  __author__ = "Lightning-AI et al."
16
16
  __author_email__ = "community@lightning.ai"
17
17
  __license__ = "Apache-2.0"
@@ -56,10 +56,9 @@ class LitAPI(ABC):
56
56
 
57
57
  return inputs
58
58
 
59
- @abstractmethod
60
59
  def predict(self, x, **kwargs):
61
60
  """Run the model on the input and return or yield the output."""
62
- pass
61
+ raise NotImplementedError("predict is not implemented")
63
62
 
64
63
  def _unbatch_no_stream(self, output):
65
64
  if isinstance(output, str):
@@ -121,6 +120,7 @@ class LitAPI(ABC):
121
120
 
122
121
  if spec:
123
122
  self._spec = spec
123
+ spec.pre_setup(self)
124
124
 
125
125
  def set_logger_queue(self, queue: Queue):
126
126
  """Set the queue for logging events."""
@@ -495,9 +495,6 @@ class LitLoop(_BaseLoop):
495
495
  self._context = {}
496
496
 
497
497
  def get_batch_requests(self, lit_api: LitAPI, request_queue: Queue, max_batch_size: int, batch_timeout: float):
498
- if max_batch_size <= 1:
499
- raise ValueError("max_batch_size must be greater than 1")
500
-
501
498
  batches, timed_out_uids = collate_requests(
502
499
  lit_api,
503
500
  request_queue,
@@ -507,8 +504,10 @@ class LitLoop(_BaseLoop):
507
504
  return batches, timed_out_uids
508
505
 
509
506
  def get_request(self, request_queue: Queue, timeout: float = 1.0):
510
- response_queue_id, uid, timestamp, x_enc = request_queue.get(timeout=timeout)
511
- return response_queue_id, uid, timestamp, x_enc
507
+ try:
508
+ return request_queue.get(timeout=timeout)
509
+ except Empty:
510
+ return None
512
511
 
513
512
  def populate_context(self, lit_spec: LitSpec, request: Any):
514
513
  if lit_spec and hasattr(lit_spec, "populate_context"):
@@ -700,19 +699,65 @@ class Output:
700
699
 
701
700
  class ContinuousBatchingLoop(LitLoop):
702
701
  def __init__(self, max_sequence_length: int = 2048):
702
+ """Runs continuous batching loop. This loop handles adding new requests, processing them in batches, and
703
+ managing the state of active sequences.
704
+
705
+ The loop requires the following methods to be implemented in the LitAPI:
706
+ - setup: sets up the model on the device
707
+ - decode_request: decodes the client request into a format that can be processed by the model
708
+ - step: generates a new token for each sequence
709
+ - encode_response: encodes the response into a format that can be sent to the client
710
+ - has_finished: checks if the sequence has finished generating
711
+
712
+ Args:
713
+ max_sequence_length (int): The maximum sequence length allowed for any active sequence.
714
+
715
+ """
703
716
  super().__init__()
704
- self.active_sequences: Dict[str, Dict] = {} # uid -> {input, current_length, generated_tokens}
717
+ self.active_sequences: Dict[str, Dict] = {} # uid -> {input, current_length, generated_sequence}
705
718
  self.max_sequence_length = max_sequence_length
706
719
  self.response_queue_ids: Dict[str, int] = {} # uid -> response_queue_id
707
720
 
721
+ def pre_setup(self, lit_api: LitAPI, spec: Optional[LitSpec]):
722
+ if not lit_api.stream:
723
+ raise ValueError(
724
+ "Continuous batching loop requires streaming to be enabled. Please set LitServe(..., stream=True)"
725
+ )
726
+
727
+ if not hasattr(lit_api, "step") and not hasattr(lit_api, "predict"):
728
+ raise ValueError("""Using the default step method with Continuous batching loop requires the lit_api to
729
+ have a `predict` method which accepts decoded request inputs and a list of generated_sequence.
730
+ Please implement the has_finished method in the lit_api.
731
+
732
+ class ExampleAPI(LitAPI):
733
+ ...
734
+ def predict(self, inputs, generated_sequence):
735
+ # implement predict logic
736
+ # return list of new tokens
737
+ ...
738
+ """)
739
+
740
+ if not hasattr(lit_api, "step") and not hasattr(lit_api, "has_finished"):
741
+ raise ValueError("""Using the default step method with Continuous batching loop
742
+ requires the lit_api to have a has_finished method. Please implement the has_finished method in the lit_api.
743
+
744
+ class ExampleAPI(LitAPI):
745
+ ...
746
+ def has_finished(self, uid: str, token: str, max_sequence_length: int) -> bool:
747
+ # implement has_finished logic
748
+ return False
749
+ """)
750
+
708
751
  def add_request(self, uid: str, request: Any, lit_api: LitAPI, lit_spec: Optional[LitSpec]) -> None:
709
- """Add a new sequence to active sequences."""
752
+ """Add a new sequence to active sequences and perform any action before prediction such as filling the cache."""
753
+ if hasattr(lit_api, "add_request"):
754
+ lit_api.add_request(uid, request)
710
755
  decoded_request = lit_api.decode_request(request)
711
- self.active_sequences[uid] = {"input": decoded_request, "current_length": 0, "generated_tokens": []}
756
+ self.active_sequences[uid] = {"input": decoded_request, "current_length": 0, "generated_sequence": []}
712
757
 
713
758
  def mark_completed(self, uid: str) -> None:
714
- """Mark a sequence as completed."""
715
- logger.info(f"Marking sequence {uid} as completed")
759
+ """Mark a request as completed and remove it from the tracked state."""
760
+ logger.debug(f"Marking sequence {uid} as completed")
716
761
  del self.active_sequences[uid]
717
762
  del self.response_queue_ids[uid]
718
763
 
@@ -725,36 +770,39 @@ class ContinuousBatchingLoop(LitLoop):
725
770
  )
726
771
  return capacity
727
772
 
728
- def step(
729
- self, prev_outputs: Optional[List[Output]], lit_api: LitAPI, lit_spec: Optional[LitSpec]
730
- ) -> List[Tuple[str, Tuple[Any, LitAPIStatus]]]:
773
+ def step(self, prev_outputs: Optional[List[Output]], lit_api: LitAPI, lit_spec: Optional[LitSpec]) -> List[Output]:
731
774
  """Process one token generation step for all active sequences."""
775
+ if hasattr(lit_api, "step"):
776
+ return lit_api.step(prev_outputs)
777
+
732
778
  if not self.active_sequences:
733
779
  return []
734
780
 
735
781
  # Batch forward pass for all active sequences
736
782
  inputs = [seq["input"] for seq in self.active_sequences.values()]
737
- generated = [seq["generated_tokens"] for seq in self.active_sequences.values()]
783
+ generated = [seq["generated_sequence"] for seq in self.active_sequences.values()]
738
784
 
739
785
  try:
740
786
  # Assume lit_api.predict handles batched token generation
741
- new_tokens = lit_api.predict(inputs, generated)
787
+ new_tokens: List[Any] = lit_api.predict(inputs, generated)
742
788
 
743
- responses = []
789
+ responses: List[Output] = []
744
790
 
745
791
  # Process each sequence's new token
746
792
  for uid, token in zip(self.active_sequences.keys(), new_tokens):
747
793
  seq = self.active_sequences[uid]
748
- seq["generated_tokens"].append(token)
794
+ seq["generated_sequence"].append(token)
749
795
  seq["current_length"] += 1
750
796
 
797
+ step_output = Output(uid, token, LitAPIStatus.OK)
798
+ responses.append(step_output)
799
+
751
800
  # Check completion conditions
752
- is_finished = lit_api.is_finished(uid, token, self.max_sequence_length)
801
+ is_finished = lit_api.has_finished(uid, token, self.max_sequence_length)
753
802
 
754
803
  if is_finished:
755
804
  # Encode final response for completed sequence
756
- response = lit_api.encode_response(seq["generated_tokens"])
757
- step_output = Output(uid, response, LitAPIStatus.FINISH_STREAMING)
805
+ step_output = Output(uid, "", LitAPIStatus.FINISH_STREAMING)
758
806
  responses.append(step_output)
759
807
 
760
808
  return responses
@@ -792,7 +840,7 @@ class ContinuousBatchingLoop(LitLoop):
792
840
  if new_batches:
793
841
  # Add new requests to pending_requests and try to process them
794
842
  for response_queue_id, uid, input in new_batches:
795
- logger.info(f"New request: {uid}, {input}")
843
+ logger.debug(f"New request: {uid}, {input}")
796
844
  if self.has_capacity(lit_api):
797
845
  self.add_request(uid, input, lit_api, lit_spec)
798
846
  self.response_queue_ids[uid] = response_queue_id
@@ -815,11 +863,6 @@ class ContinuousBatchingLoop(LitLoop):
815
863
  workers_setup_status: Dict[int, str],
816
864
  callback_runner: CallbackRunner,
817
865
  ):
818
- if not lit_api.stream:
819
- raise ValueError(
820
- "Continuous batching loop requires streaming to be enabled. Please set LitServe(..., stream=True)"
821
- )
822
-
823
866
  """Main loop that processes batches of requests."""
824
867
  pending_requests = self.prefill(
825
868
  [],
@@ -846,10 +889,11 @@ class ContinuousBatchingLoop(LitLoop):
846
889
  for step_output in responses:
847
890
  logger.debug(f"Processing response: {step_output}")
848
891
  status = step_output.status
849
- response_data = step_output.output
892
+ response_data = lit_api.encode_response(step_output.output)
850
893
  uid = step_output.uid
851
894
  response_queue_id = self.response_queue_ids[uid]
852
895
 
896
+ response_data = lit_api.format_encoded_response(response_data)
853
897
  if status == LitAPIStatus.ERROR:
854
898
  self.put_error_response(response_queues, response_queue_id, uid, response_data)
855
899
  self.mark_completed(uid)
@@ -870,7 +914,7 @@ class ContinuousBatchingLoop(LitLoop):
870
914
  )
871
915
 
872
916
  except Exception as e:
873
- logger.exception("Error in continuous batching loop")
917
+ logger.exception(f"Error in continuous batching loop: {e}")
874
918
  # Handle any errors by sending error responses for all tracked requests
875
919
  for uid, response_queue_id in self.response_queue_ids.items():
876
920
  self.put_error_response(response_queues, response_queue_id, uid, e)
@@ -15,7 +15,7 @@ from abc import abstractmethod
15
15
  from typing import TYPE_CHECKING, Callable, List
16
16
 
17
17
  if TYPE_CHECKING:
18
- from litserve import LitServer
18
+ from litserve import LitAPI, LitServer
19
19
 
20
20
 
21
21
  class LitSpec:
@@ -26,6 +26,9 @@ class LitSpec:
26
26
 
27
27
  self._server: LitServer = None
28
28
 
29
+ def pre_setup(self, lit_api: "LitAPI"):
30
+ pass
31
+
29
32
  def setup(self, server: "LitServer"):
30
33
  self._server = server
31
34
 
@@ -30,7 +30,7 @@ from litserve.specs.base import LitSpec
30
30
  from litserve.utils import LitAPIStatus, azip
31
31
 
32
32
  if typing.TYPE_CHECKING:
33
- from litserve import LitServer
33
+ from litserve import LitAPI, LitServer
34
34
 
35
35
  logger = logging.getLogger(__name__)
36
36
 
@@ -262,18 +262,18 @@ class OpenAISpec(LitSpec):
262
262
  self.add_endpoint("/v1/chat/completions", self.chat_completion, ["POST"])
263
263
  self.add_endpoint("/v1/chat/completions", self.options_chat_completions, ["OPTIONS"])
264
264
 
265
- def setup(self, server: "LitServer"):
265
+ def pre_setup(self, lit_api: "LitAPI"):
266
266
  from litserve import LitAPI
267
267
 
268
- super().setup(server)
269
-
270
- lit_api = self._server.lit_api
271
268
  if not inspect.isgeneratorfunction(lit_api.predict):
272
269
  raise ValueError(LITAPI_VALIDATION_MSG.format("predict is not a generator"))
273
270
 
274
271
  is_encode_response_original = lit_api.encode_response.__code__ is LitAPI.encode_response.__code__
275
272
  if not is_encode_response_original and not inspect.isgeneratorfunction(lit_api.encode_response):
276
273
  raise ValueError(LITAPI_VALIDATION_MSG.format("encode_response is not a generator"))
274
+
275
+ def setup(self, server: "LitServer"):
276
+ super().setup(server)
277
277
  print("OpenAI spec setup complete")
278
278
 
279
279
  def populate_context(self, context, request):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: litserve
3
- Version: 0.2.6.dev1
3
+ Version: 0.2.6.dev2
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,10 @@ 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
+ Provides-Extra: perf
33
+ Requires-Dist: jsonargparse; extra == "perf"
34
+ Requires-Dist: tenacity; extra == "perf"
35
+ Requires-Dist: uvloop; extra == "perf"
32
36
  Provides-Extra: test
33
37
  Requires-Dist: asgi-lifespan; extra == "test"
34
38
  Requires-Dist: coverage[toml]>=7.5.3; extra == "test"
@@ -47,10 +51,6 @@ Requires-Dist: python-multipart; extra == "test"
47
51
  Requires-Dist: requests; extra == "test"
48
52
  Requires-Dist: torch>2.0.0; extra == "test"
49
53
  Requires-Dist: transformers; extra == "test"
50
- Provides-Extra: perf
51
- Requires-Dist: jsonargparse; extra == "perf"
52
- Requires-Dist: tenacity; extra == "perf"
53
- Requires-Dist: uvloop; extra == "perf"
54
54
 
55
55
  <div align='center'>
56
56
 
File without changes
File without changes
File without changes
File without changes
File without changes