MindSpore Serving的Servable包含两种类型。一种是推理服务来源于单模型,一种是推理服务来源于多模型组合,多模型组合当前不支持。
本文将说明如何对单模型进行配置以提供Servable,以下所有Servable配置说明针对的是单模型Servable,Serving客户端简称客户端。
模型提供推理能力,模型的每个输入和输出的数据类型、数据长度、Shape是固定的。
如果客户端发来的数据不能直接满足模型输入要求,需要通过预处理转化为满足模型输入的数据。
如果模型的输出不直接提供给客户端,需要通过后处理转化为所需的输出数据。
比如,针对Resnet推理模型,客户端发来的数据为jpg、png等格式的图片,预期返回图片的分类。Resnet模型输入为经过图片Decode、Resize、Normalize等操作产生的Tensor,输出为每个类别的得分Tensor。需要通过预处理将图片转化为满足模型输入的Tensor,通过后处理返回得分最大的类别名称或者前5类别名称及其得分。
在不同的场景下,如果来自客户端的数据输入组成、结构或类型不同,可以提供不同的预处理。如果对模型的输出也有不同的要求,可以提供不同的后处理。比如上述resnet Servable,针对返回得分最大的类别名称还是前5类别名称及其得分这两种场景提供了两个后处理。
上述的resnet Servable提供了classify_top5和classify_top1两个方法(Method)。classify_top5输入为image,输出为label和score,返回前5的分类名称和得分。classify_top1预处理和classify_top5一致,而后处理不同,输入为image,输出为label,返回最大得分的分类名称。
一个Servable可提供一个或多个方法,Servable的名称和方法的名称标记了Serving提供的一个服务,每个方法对客户端提供的数据进行可选的预处理,接着进行模型推理,对模型的推理结果进行可选的后处理,最后将需要的结果返回给客户端。
即,每个方法:
每次请求可包括一个或多个实例,每个实例之间相互独立,结果互不影响。比如一张图片返回一个分类类别,三张独立的图片独立返回三个分类类别。
以Resnet模型为例,模型配置文件目录结果如下图所示:
resnet/ ├── 1 │ └── resnet_classify.minir ├── 2 │ └── resnet_classify.minir └── servable_config.py
目录resnet指示Servable的名称。
通过servable_config.py配置Servable,其中包括预处理和后处理定义、模型声明、方法定义。
目录1和2表示版本1和版本2的模型,模型版本为正整数,从1开始,数字越大表示版本越新。
预处理和后处理定义方式例子如下:
def resnet_preprocess(instances): # instances: tuple of instance
for instance in instances: # instance: tuple of input
image = instance[0] # input 0
# input1 = instance[1] # input 1 if exist
# Decode, Resize, Normalize
image_result = ...
yield (image_result,)
idx_to_label = ["person", "car", ...]
def resnet_postprocess_top1(instances):
for instance in instances:
score = instance[0] # get input 0
max_idx = np.argmax(score)
yield (idx_to_label[max_idx],)
def resnet_postprocess_top5(instances):
for instance in instances:
score = instance[0] # get input 0
ids = np.argsort(score)[:5] # top 5
ret_label = idx_to_label[ids]
ret_score = score[ids]
yield (",".join(ret_label), ret_score)
预处理和后处理定义格式相同,入参为实例数据组成的tuple,每个实例数据为输入数据组成的tuple,每个输入数据为numpy对象,通过yield返回实例的处理结果,yield返回的数据类型可为numpy对象、Python的bool、int、float、str、bytes组成的tuple。
预处理和后处理输入的来源和输出的使用由方法定义决定。
from mindspore_serving.worker import register
register.declare_servable(servable_file="resnet_classify.minir", model_format="MindIR", with_batch_dim=True)
其中declare_servable入参servable_file指示模型的文件名称;model_format指示模型的模型类别,当前Ascend310环境支持OM和MindIR两种模型类型,Ascend910环境仅支持MindIR模型类型。
如果模型输入输出第1维度不是batch维度,需要设置参数with_batch_dim=False,with_batch_dim默认为True。
with_batch_dim为True,主要针对处理图片、文本等包含batch维度的模型。假设batch_size=2,当前请求有3个实例,共3张图片,会拆分为2次模型推理,第1次处理2张图片返回2个结果,第2次对剩余的1张图片进行拷贝做一次推理并返回1个结果,最终返回3个结果。
with_batch_dim为False,主要针对不涉及或不考虑batch维度的模型。比如输入输出为二维Tensor的矩阵乘模型。请求的每个实例将单独作一次推理任务。
方法定义的例子如下:
@register.register_method(output_names=["label"])
def classify_top1(image):
x = register.call_preprocess(resnet_preprocess, image)
x = register.call_servable(x)
x = register.call_postprocess(resnet_postprocess_top1, x)
return x
@register.register_method(output_names=["label", "score"])
def classify_top5(image):
x = register.call_preprocess(resnet_preprocess, image)
x = register.call_servable(x)
label, score = register.call_postprocess(resnet_postprocess_top5, x)
return label, score
Python函数和Servable方法对应关系如下表:
| Python函数 | Servable方法 |
|---|---|
| 函数名 | 方法名 |
| 入参和入参名称 | 入参和入参名称 |
register_method的output_names参数 |
出参和出参名称 |
call_preprocess指示了使用的预处理及其输入。
call_servable指示了模型推理的输入。
call_postprocess指示了使用的后处理及其输入。
return指示了方法的返回数据,和register_method的output_names参数对应。
方法定义不能包括if、for、while等分支结构,预处理和后处理可选,模型推理必选,且顺序不能打乱。