Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
FROM python:3.10-slim AS wheel-builder
SHELL ["/bin/bash", "-l", "-c"]

ARG POETRY_VERSION="1.8.3"
# TODO: Upgrade poetry version to 2.x
# Currently it fails in `poetry export --with all-runtimes --without-hashes --format constraints.txt -o /opt/mlserver/dist/constraints.txt`
ARG POETRY_VERSION="1.8.5"

COPY ./hack/build-wheels.sh ./hack/build-wheels.sh
COPY ./mlserver ./mlserver
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ install-dev:

lock:
echo "Locking mlserver deps..."
poetry lock --no-update
poetry lock
for _runtime in ./runtimes/*; \
do \
echo "Locking $$_runtime deps..."; \
poetry lock --no-update -C $$_runtime; \
poetry lock -C $$_runtime; \
done

_generate_model_repository: # "private" target to call `fmt` after `generate`
Expand Down
2 changes: 1 addition & 1 deletion mlserver/batching/adaptive.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ async def _batch_requests(self) -> AsyncIterator[BatchedRequests]:

# Update remaining timeout
current = time.time()
timeout = timeout - (current - start)
timeout = self._max_batch_time - (current - start)
except asyncio.TimeoutError:
# NOTE: Hit timeout, continue
pass
Expand Down
38 changes: 36 additions & 2 deletions mlserver/codecs/json.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# seperate file to side step circular dependency on the decode_str function

from typing import Any, Union
import json
import numpy as np
from typing import Any, List, Union

try:
import orjson
except ImportError:
orjson = None # type: ignore

from .string import decode_str
from .lists import as_list
from .utils import InputOrOutput


# originally taken from: mlserver/rest/responses.py
Expand Down Expand Up @@ -57,5 +60,36 @@ def encode_to_json_bytes(v: Any) -> bytes:
def decode_from_bytelike_json_to_dict(v: Union[bytes, str]) -> dict:
if orjson is None:
return json.loads(v)

return orjson.loads(v)


class JSONEncoderWithArray(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.integer):
return int(obj)
else:
return json.JSONEncoder.default(self, obj)


def encode_to_json(v: Any, use_bytes: bool = True) -> Union[str, bytes]:
enc_v = json.dumps(
v,
ensure_ascii=False,
allow_nan=False,
indent=None,
separators=(",", ":"),
cls=JSONEncoderWithArray,
)
if use_bytes:
enc_v = enc_v.encode("utf-8") # type: ignore[assignment]
return enc_v


def decode_json_input_or_output(input_or_output: InputOrOutput) -> List[Any]:
packed = input_or_output.data.root
unpacked = map(json.loads, as_list(packed))
return list(unpacked)
18 changes: 15 additions & 3 deletions mlserver/codecs/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from typing import Optional, Any, List, Tuple

from .base import RequestCodec, register_request_codec
from .numpy import to_datatype, to_dtype, convert_nan
from .numpy import to_dtype, convert_nan, to_datatype
from .json import decode_json_input_or_output, encode_to_json
from .string import encode_str, StringCodec
from .utils import get_decoded_or_raw, InputOrOutput, inject_batch_dimension
from .lists import ListElement
Expand All @@ -19,8 +20,12 @@


def _to_series(input_or_output: InputOrOutput) -> pd.Series:
payload = get_decoded_or_raw(input_or_output)
parameters = input_or_output.parameters

if parameters and parameters.content_type == PandasCodec.JsonContentType:
return pd.Series(decode_json_input_or_output(input_or_output))

payload = get_decoded_or_raw(input_or_output)
if Datatype(input_or_output.datatype) == Datatype.BYTES:
# Don't convert the dtype of BYTES
return pd.Series(payload)
Expand All @@ -43,7 +48,13 @@ def _to_response_output(series: pd.Series, use_bytes: bool = True) -> ResponseOu

content_type = None
if datatype == Datatype.BYTES:
data, content_type = _process_bytes(data, use_bytes)
processed_data, content_type = _process_bytes(data, use_bytes)

if content_type is None:
data = [encode_to_json(elem, use_bytes) for elem in data]
content_type = PandasCodec.JsonContentType
else:
data = processed_data

shape = inject_batch_dimension(list(series.shape))
parameters = None
Expand Down Expand Up @@ -90,6 +101,7 @@ class PandasCodec(RequestCodec):
"""

ContentType = "pd"
JsonContentType = "pd_json"
TypeHint = pd.DataFrame

@classmethod
Expand Down
2 changes: 1 addition & 1 deletion mlserver/grpc/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _create_server(self):

self._server = aio.server(
ThreadPoolExecutor(max_workers=DefaultGrpcWorkers),
interceptors=tuple(self._interceptors),
interceptors=list(self._interceptors),
options=self._get_options(),
)

Expand Down
Loading
Loading