prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Consider using `tobytes()` instead of `tolist()` in numpy codec for performance improvement
  Our use-case is to make multiple requests to MLServer and all of them will have a 640x640 image data in numpy format as input.
  
  Given that `tobytes()` is way faster than `tolist()` does it make sense to support it by default in the numpy codec here: https://github.com/SeldonIO/MLServer/blob/61d9f078d1f886d14083551f33db6a3146f12745/mlserver/codecs/numpy.py#L54
  
  ----
  
  Performance comparison of the two methods:
  
  ![Screenshot 2021-07-31 at 9 19 32 PM](https://user-images.githubusercontent.com/161805/127745419-63e80509-ce32-44b2-8b2a-b23636c764d6.jpg)
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `mlserver/codecs/numpy.py`
  Content:
  ```
  1 import numpy as np
  2 
  3 from ..types import RequestInput, ResponseOutput
  4 
  5 from .base import InputCodec, register_input_codec, register_request_codec
  6 from .utils import FirstInputRequestCodec
  7 
  8 _DatatypeToNumpy = {
  9     "BOOL": "bool",
  10     "UINT8": "uint8",
  11     "UINT16": "uint16",
  12     "UINT32": "uint32",
  13     "UINT64": "uint64",
  14     "INT8": "int8",
  15     "INT16": "int16",
  16     "INT32": "int32",
  17     "INT64": "int64",
  18     "FP16": "float16",
  19     "FP32": "float32",
  20     "FP64": "float64",
  21     "BYTES": "byte",
  22 }
  23 
  24 _NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}
  25 
  26 # NOTE: numpy has more types than v2 protocol
  27 _NumpyToDatatype["object"] = "BYTES"
  28 
  29 
  30 def _to_dtype(datatype: str) -> "np.dtype":
  31     dtype = _DatatypeToNumpy[datatype]
  32     return np.dtype(dtype)
  33 
  34 
  35 def _to_datatype(dtype: np.dtype) -> str:
  36     as_str = str(dtype)
  37     datatype = _NumpyToDatatype[as_str]
  38 
  39     return datatype
  40 
  41 
  42 @register_input_codec
  43 class NumpyCodec(InputCodec):
  44     """
  45     Encodes a tensor as a numpy array.
  46     """
  47 
  48     ContentType = "np"
  49 
  50     @classmethod
  51     def encode(cls, name: str, payload: np.ndarray) -> ResponseOutput:
  52         return ResponseOutput(
  53             name=name,
  54             datatype=_to_datatype(payload.dtype),
  55             shape=list(payload.shape),
  56             data=payload.flatten().tolist(),
  57         )
  58 
  59     @classmethod
  60     def decode(cls, request_input: RequestInput) -> np.ndarray:
  61         dtype = _to_dtype(request_input.datatype)
  62         data = getattr(request_input.data, "__root__", request_input.data)
  63 
  64         model_data = np.array(data, dtype=dtype)
  65 
  66         # TODO: Check if reshape not valid
  67         return model_data.reshape(request_input.shape)
  68 
  69 
  70 @register_request_codec
  71 class NumpyRequestCodec(FirstInputRequestCodec):
  72     InputCodec = NumpyCodec
  73     ContentType = NumpyCodec.ContentType
  74
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/mlserver/codecs/numpy.py b/mlserver/codecs/numpy.py
  --- a/mlserver/codecs/numpy.py
  +++ b/mlserver/codecs/numpy.py
  @@ -1,5 +1,7 @@
   import numpy as np
   
  +from typing import Any
  +
   from ..types import RequestInput, ResponseOutput
   
   from .base import InputCodec, register_input_codec, register_request_codec
  @@ -18,27 +20,59 @@
       "FP16": "float16",
       "FP32": "float32",
       "FP64": "float64",
  -    "BYTES": "byte",
  +    "BYTES": "bytes",
   }
   
   _NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}
   
   # NOTE: numpy has more types than v2 protocol
   _NumpyToDatatype["object"] = "BYTES"
  +_NumpyToDatatype["S"] = "BYTES"
  +
   
  +def _to_dtype(request_input: RequestInput) -> "np.dtype":
  +    dtype = _DatatypeToNumpy[request_input.datatype]
  +
  +    if request_input.datatype == "BYTES":
  +        # bytes have variable size, so need to specify as part of type
  +        # TODO: Make elem size variable (and not just the last dimension)
  +        elem_size = request_input.shape[-1]
  +        return np.dtype((dtype, elem_size))
   
  -def _to_dtype(datatype: str) -> "np.dtype":
  -    dtype = _DatatypeToNumpy[datatype]
       return np.dtype(dtype)
   
   
   def _to_datatype(dtype: np.dtype) -> str:
       as_str = str(dtype)
  +
  +    if as_str not in _NumpyToDatatype:
  +        # If not present, try with kind
  +        as_str = getattr(dtype, "kind")
  +
       datatype = _NumpyToDatatype[as_str]
   
       return datatype
   
   
  +def _to_ndarray(request_input: RequestInput) -> np.ndarray:
  +    data = getattr(request_input.data, "__root__", request_input.data)
  +    dtype = _to_dtype(request_input)
  +
  +    if request_input.datatype == "BYTES":
  +        return np.frombuffer(data, dtype)
  +
  +    return np.array(data, dtype)
  +
  +
  +def _encode_data(data: np.ndarray, datatype: str) -> Any:
  +    if datatype == "BYTES":
  +        # tobytes is way faster than tolist, although it's harder to serialise
  +        # and only makes sense for actual bytes inputs (#253)
  +        return data.tobytes()
  +
  +    return data.flatten().tolist()
  +
  +
   @register_input_codec
   class NumpyCodec(InputCodec):
       """
  @@ -49,19 +83,18 @@
   
       @classmethod
       def encode(cls, name: str, payload: np.ndarray) -> ResponseOutput:
  +        datatype = _to_datatype(payload.dtype)
  +
           return ResponseOutput(
               name=name,
  -            datatype=_to_datatype(payload.dtype),
  +            datatype=datatype,
               shape=list(payload.shape),
  -            data=payload.flatten().tolist(),
  +            data=_encode_data(payload, datatype),
           )
   
       @classmethod
       def decode(cls, request_input: RequestInput) -> np.ndarray:
  -        dtype = _to_dtype(request_input.datatype)
  -        data = getattr(request_input.data, "__root__", request_input.data)
  -
  -        model_data = np.array(data, dtype=dtype)
  +        model_data = _to_ndarray(request_input)
   
           # TODO: Check if reshape not valid
           return model_data.reshape(request_input.shape)
  
verification_info: {"golden_diff": "diff --git a/mlserver/codecs/numpy.py b/mlserver/codecs/numpy.py\n--- a/mlserver/codecs/numpy.py\n+++ b/mlserver/codecs/numpy.py\n@@ -1,5 +1,7 @@\n import numpy as np\n \n+from typing import Any\n+\n from ..types import RequestInput, ResponseOutput\n \n from .base import InputCodec, register_input_codec, register_request_codec\n@@ -18,27 +20,59 @@\n     \"FP16\": \"float16\",\n     \"FP32\": \"float32\",\n     \"FP64\": \"float64\",\n-    \"BYTES\": \"byte\",\n+    \"BYTES\": \"bytes\",\n }\n \n _NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}\n \n # NOTE: numpy has more types than v2 protocol\n _NumpyToDatatype[\"object\"] = \"BYTES\"\n+_NumpyToDatatype[\"S\"] = \"BYTES\"\n+\n \n+def _to_dtype(request_input: RequestInput) -> \"np.dtype\":\n+    dtype = _DatatypeToNumpy[request_input.datatype]\n+\n+    if request_input.datatype == \"BYTES\":\n+        # bytes have variable size, so need to specify as part of type\n+        # TODO: Make elem size variable (and not just the last dimension)\n+        elem_size = request_input.shape[-1]\n+        return np.dtype((dtype, elem_size))\n \n-def _to_dtype(datatype: str) -> \"np.dtype\":\n-    dtype = _DatatypeToNumpy[datatype]\n     return np.dtype(dtype)\n \n \n def _to_datatype(dtype: np.dtype) -> str:\n     as_str = str(dtype)\n+\n+    if as_str not in _NumpyToDatatype:\n+        # If not present, try with kind\n+        as_str = getattr(dtype, \"kind\")\n+\n     datatype = _NumpyToDatatype[as_str]\n \n     return datatype\n \n \n+def _to_ndarray(request_input: RequestInput) -> np.ndarray:\n+    data = getattr(request_input.data, \"__root__\", request_input.data)\n+    dtype = _to_dtype(request_input)\n+\n+    if request_input.datatype == \"BYTES\":\n+        return np.frombuffer(data, dtype)\n+\n+    return np.array(data, dtype)\n+\n+\n+def _encode_data(data: np.ndarray, datatype: str) -> Any:\n+    if datatype == \"BYTES\":\n+        # tobytes is way faster than tolist, although it's harder to serialise\n+        # and only makes sense for actual bytes inputs (#253)\n+        return data.tobytes()\n+\n+    return data.flatten().tolist()\n+\n+\n @register_input_codec\n class NumpyCodec(InputCodec):\n     \"\"\"\n@@ -49,19 +83,18 @@\n \n     @classmethod\n     def encode(cls, name: str, payload: np.ndarray) -> ResponseOutput:\n+        datatype = _to_datatype(payload.dtype)\n+\n         return ResponseOutput(\n             name=name,\n-            datatype=_to_datatype(payload.dtype),\n+            datatype=datatype,\n             shape=list(payload.shape),\n-            data=payload.flatten().tolist(),\n+            data=_encode_data(payload, datatype),\n         )\n \n     @classmethod\n     def decode(cls, request_input: RequestInput) -> np.ndarray:\n-        dtype = _to_dtype(request_input.datatype)\n-        data = getattr(request_input.data, \"__root__\", request_input.data)\n-\n-        model_data = np.array(data, dtype=dtype)\n+        model_data = _to_ndarray(request_input)\n \n         # TODO: Check if reshape not valid\n         return model_data.reshape(request_input.shape)\n", "issue": "Consider using `tobytes()` instead of `tolist()` in numpy codec for performance improvement\nOur use-case is to make multiple requests to MLServer and all of them will have a 640x640 image data in numpy format as input.\r\n\r\nGiven that `tobytes()` is way faster than `tolist()` does it make sense to support it by default in the numpy codec here: https://github.com/SeldonIO/MLServer/blob/61d9f078d1f886d14083551f33db6a3146f12745/mlserver/codecs/numpy.py#L54\r\n\r\n----\r\n\r\nPerformance comparison of the two methods:\r\n\r\n![Screenshot 2021-07-31 at 9 19 32 PM](https://user-images.githubusercontent.com/161805/127745419-63e80509-ce32-44b2-8b2a-b23636c764d6.jpg)\r\n\n", "before_files": [{"content": "import numpy as np\n\nfrom ..types import RequestInput, ResponseOutput\n\nfrom .base import InputCodec, register_input_codec, register_request_codec\nfrom .utils import FirstInputRequestCodec\n\n_DatatypeToNumpy = {\n    \"BOOL\": \"bool\",\n    \"UINT8\": \"uint8\",\n    \"UINT16\": \"uint16\",\n    \"UINT32\": \"uint32\",\n    \"UINT64\": \"uint64\",\n    \"INT8\": \"int8\",\n    \"INT16\": \"int16\",\n    \"INT32\": \"int32\",\n    \"INT64\": \"int64\",\n    \"FP16\": \"float16\",\n    \"FP32\": \"float32\",\n    \"FP64\": \"float64\",\n    \"BYTES\": \"byte\",\n}\n\n_NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}\n\n# NOTE: numpy has more types than v2 protocol\n_NumpyToDatatype[\"object\"] = \"BYTES\"\n\n\ndef _to_dtype(datatype: str) -> \"np.dtype\":\n    dtype = _DatatypeToNumpy[datatype]\n    return np.dtype(dtype)\n\n\ndef _to_datatype(dtype: np.dtype) -> str:\n    as_str = str(dtype)\n    datatype = _NumpyToDatatype[as_str]\n\n    return datatype\n\n\n@register_input_codec\nclass NumpyCodec(InputCodec):\n    \"\"\"\n    Encodes a tensor as a numpy array.\n    \"\"\"\n\n    ContentType = \"np\"\n\n    @classmethod\n    def encode(cls, name: str, payload: np.ndarray) -> ResponseOutput:\n        return ResponseOutput(\n            name=name,\n            datatype=_to_datatype(payload.dtype),\n            shape=list(payload.shape),\n            data=payload.flatten().tolist(),\n        )\n\n    @classmethod\n    def decode(cls, request_input: RequestInput) -> np.ndarray:\n        dtype = _to_dtype(request_input.datatype)\n        data = getattr(request_input.data, \"__root__\", request_input.data)\n\n        model_data = np.array(data, dtype=dtype)\n\n        # TODO: Check if reshape not valid\n        return model_data.reshape(request_input.shape)\n\n\n@register_request_codec\nclass NumpyRequestCodec(FirstInputRequestCodec):\n    InputCodec = NumpyCodec\n    ContentType = NumpyCodec.ContentType\n", "path": "mlserver/codecs/numpy.py"}], "after_files": [{"content": "import numpy as np\n\nfrom typing import Any\n\nfrom ..types import RequestInput, ResponseOutput\n\nfrom .base import InputCodec, register_input_codec, register_request_codec\nfrom .utils import FirstInputRequestCodec\n\n_DatatypeToNumpy = {\n    \"BOOL\": \"bool\",\n    \"UINT8\": \"uint8\",\n    \"UINT16\": \"uint16\",\n    \"UINT32\": \"uint32\",\n    \"UINT64\": \"uint64\",\n    \"INT8\": \"int8\",\n    \"INT16\": \"int16\",\n    \"INT32\": \"int32\",\n    \"INT64\": \"int64\",\n    \"FP16\": \"float16\",\n    \"FP32\": \"float32\",\n    \"FP64\": \"float64\",\n    \"BYTES\": \"bytes\",\n}\n\n_NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}\n\n# NOTE: numpy has more types than v2 protocol\n_NumpyToDatatype[\"object\"] = \"BYTES\"\n_NumpyToDatatype[\"S\"] = \"BYTES\"\n\n\ndef _to_dtype(request_input: RequestInput) -> \"np.dtype\":\n    dtype = _DatatypeToNumpy[request_input.datatype]\n\n    if request_input.datatype == \"BYTES\":\n        # bytes have variable size, so need to specify as part of type\n        # TODO: Make elem size variable (and not just the last dimension)\n        elem_size = request_input.shape[-1]\n        return np.dtype((dtype, elem_size))\n\n    return np.dtype(dtype)\n\n\ndef _to_datatype(dtype: np.dtype) -> str:\n    as_str = str(dtype)\n\n    if as_str not in _NumpyToDatatype:\n        # If not present, try with kind\n        as_str = getattr(dtype, \"kind\")\n\n    datatype = _NumpyToDatatype[as_str]\n\n    return datatype\n\n\ndef _to_ndarray(request_input: RequestInput) -> np.ndarray:\n    data = getattr(request_input.data, \"__root__\", request_input.data)\n    dtype = _to_dtype(request_input)\n\n    if request_input.datatype == \"BYTES\":\n        return np.frombuffer(data, dtype)\n\n    return np.array(data, dtype)\n\n\ndef _encode_data(data: np.ndarray, datatype: str) -> Any:\n    if datatype == \"BYTES\":\n        # tobytes is way faster than tolist, although it's harder to serialise\n        # and only makes sense for actual bytes inputs (#253)\n        return data.tobytes()\n\n    return data.flatten().tolist()\n\n\n@register_input_codec\nclass NumpyCodec(InputCodec):\n    \"\"\"\n    Encodes a tensor as a numpy array.\n    \"\"\"\n\n    ContentType = \"np\"\n\n    @classmethod\n    def encode(cls, name: str, payload: np.ndarray) -> ResponseOutput:\n        datatype = _to_datatype(payload.dtype)\n\n        return ResponseOutput(\n            name=name,\n            datatype=datatype,\n            shape=list(payload.shape),\n            data=_encode_data(payload, datatype),\n        )\n\n    @classmethod\n    def decode(cls, request_input: RequestInput) -> np.ndarray:\n        model_data = _to_ndarray(request_input)\n\n        # TODO: Check if reshape not valid\n        return model_data.reshape(request_input.shape)\n\n\n@register_request_codec\nclass NumpyRequestCodec(FirstInputRequestCodec):\n    InputCodec = NumpyCodec\n    ContentType = NumpyCodec.ContentType\n", "path": "mlserver/codecs/numpy.py"}]}