Files
16d5102072 Fix: Headers passing for v1/v2 endpoints (#3669)
* Initial commit for headers passing issue

Signed-off-by: Andrews Arokiam <[email protected]>

* modifying the e2e test for rebase conflict

Signed-off-by: Andrews Arokiam <[email protected]>

* bug fix on unittest

Signed-off-by: Andrews Arokiam <[email protected]>

* review changes

Signed-off-by: Andrews Arokiam <[email protected]>

* fix for test failure

Signed-off-by: Andrews Arokiam <[email protected]>

* bug fix on e2e test

Signed-off-by: Andrews Arokiam <[email protected]>

* overridding the entrypoint of custom model images

Signed-off-by: Andrews Arokiam <[email protected]>

* custom response header

Signed-off-by: Andrews Arokiam <[email protected]>

* fix for unittest failure

Signed-off-by: Andrews Arokiam <[email protected]>

* added custom response headers in post process

Signed-off-by: Andrews Arokiam <[email protected]>

* added predict time latency in example response header

Signed-off-by: Andrews Arokiam <[email protected]>

* fix OOM

---------

Signed-off-by: Andrews Arokiam <[email protected]>
Co-authored-by: Dan Sun <[email protected]>
2024-10-12 10:34:55 -04:00

151 lines
5.3 KiB
Python

# Copyright 2021 The KServe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
from fastapi.middleware.cors import CORSMiddleware
from torchvision import models, transforms
from typing import Dict, Union
import torch
import time
from PIL import Image
import base64
import io
import numpy as np
from kserve import (
Model,
ModelServer,
model_server,
InferRequest,
InferOutput,
InferResponse,
logging,
)
from kserve.errors import InvalidInput
from kserve.model_server import app
from kserve.utils.utils import generate_uuid
# This custom predictor example implements the custom model following KServe REST v1/v2 protocol,
# the input can be raw image base64 encoded bytes or image tensor which is pre-processed by transformer
# and then passed to the custom predictor, the output is the prediction response.
class AlexNetModel(Model):
def __init__(self, name: str):
super().__init__(name, return_response_headers=True)
self.model = None
self.ready = False
self.load()
def load(self):
self.model = models.alexnet(pretrained=True, progress=False)
self.model.eval()
# The ready flag is used by model ready endpoint for readiness probes,
# set to True when model is loaded successfully without exceptions.
self.ready = True
def preprocess(
self, payload: Union[Dict, InferRequest], headers: Dict[str, str] = None
) -> torch.Tensor:
raw_img_data = None
if isinstance(payload, Dict) and "instances" in payload:
headers["request-type"] = "v1"
if "data" in payload["instances"][0]:
# assume the data is already preprocessed in transformer
np_array = np.asarray(payload["instances"][0]["data"])
input_tensor = torch.Tensor(np_array)
return input_tensor.unsqueeze(0)
elif "image" in payload["instances"][0]:
# Input follows the Tensorflow V1 HTTP API for binary values
# https://www.tensorflow.org/tfx/serving/api_rest#encoding_binary_values
img_data = payload["instances"][0]["image"]["b64"]
raw_img_data = base64.b64decode(img_data)
elif isinstance(payload, InferRequest):
infer_input = payload.inputs[0]
if infer_input.datatype == "BYTES":
if payload.from_grpc:
raw_img_data = infer_input.data[0]
else:
raw_img_data = base64.b64decode(infer_input.data[0])
elif infer_input.datatype == "FP32":
# assume the data is already preprocessed in transformer
input_np = infer_input.as_numpy()
return torch.Tensor(input_np)
else:
raise InvalidInput("invalid payload")
input_image = Image.open(io.BytesIO(raw_img_data))
preprocess = transforms.Compose(
[
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
),
]
)
input_tensor = preprocess(input_image)
return input_tensor.unsqueeze(0)
def predict(
self,
input_tensor: torch.Tensor,
headers: Dict[str, str] = None,
response_headers: Dict[str, str] = None,
) -> Union[Dict, InferResponse]:
start = time.time()
output = self.model(input_tensor)
torch.nn.functional.softmax(output, dim=1)
values, top_5 = torch.topk(output, 5)
result = values.flatten().tolist()
response_id = generate_uuid()
infer_output = InferOutput(
name="output-0", shape=list(values.shape), datatype="FP32", data=result
)
infer_response = InferResponse(
model_name=self.name, infer_outputs=[infer_output], response_id=response_id
)
end = time.time()
# Example for custom response headers
if response_headers is not None:
response_headers.update(
{"prediction-time-latency": f"{round((end - start) * 1000, 9)}"}
)
if "request-type" in headers and headers["request-type"] == "v1":
return {"predictions": result}
else:
return infer_response
parser = argparse.ArgumentParser(parents=[model_server.parser])
args, _ = parser.parse_known_args()
if __name__ == "__main__":
if args.configure_logging:
logging.configure_logging(args.log_config_file)
model = AlexNetModel(args.model_name)
model.load()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
ModelServer().start([model])