基于MindSpore Serving部署推理服务

Linux Ascend GPU Serving 初级 中级 高级

概述

MindSpore Serving是一个轻量级、高性能的服务模块,旨在帮助MindSpore开发者在生产环境中高效部署在线推理服务。当用户使用MindSpore完成模型训练后,导出MindSpore模型,即可使用MindSpore Serving创建该模型的推理服务。

本文以一个简单的Add网络为例,演示MindSpore Serving如何使用。

环境准备

运行示例前,需确保已经正确安装了MindSpore Serving。如果没有,可以通过MindSpore Serving安装页面,将MindSpore Serving正确地安装到你的电脑当中,同时通过MindSpore Serving环境配置页面完成环境变量配置。

导出模型

使用add_model.py,构造一个只有Add算子的网络,并导出MindSpore推理部署模型。

import os
from shutil import copyfile
import numpy as np

import mindspore.context as context
import mindspore.nn as nn
import mindspore.ops as ops
import mindspore as ms

context.set_context(mode=context.GRAPH_MODE, device_target="Ascend")


class Net(nn.Cell):
    """Define Net of add"""

    def __init__(self):
        super(Net, self).__init__()
        self.add = ops.Add()

    def construct(self, x_, y_):
        """construct add net"""
        return self.add(x_, y_)


def export_net():
    """Export add net of 2x2 + 2x2, and copy output model `tensor_add.mindir` to directory ../add/1"""
    x = np.ones([2, 2]).astype(np.float32)
    y = np.ones([2, 2]).astype(np.float32)
    add = Net()
    output = add(ms.Tensor(x), ms.Tensor(y))
    ms.export(add, ms.Tensor(x), ms.Tensor(y), file_name='tensor_add', file_format='MINDIR')
    dst_dir = '../add/1'
    try:
        os.mkdir(dst_dir)
    except OSError:
        pass

    dst_file = os.path.join(dst_dir, 'tensor_add.mindir')
    copyfile('tensor_add.mindir', dst_file)
    print("copy tensor_add.mindir to " + dst_dir + " success")

    print(x)
    print(y)
    print(output.asnumpy())


if __name__ == "__main__":
    export_net()

使用MindSpore定义神经网络需要继承mindspore.nn.Cell。Cell是所有神经网络的基类。神经网络的各层需要预先在__init__方法中定义,然后通过定义construct方法来完成神经网络的前向构造。使用mindspore模块的export即可导出模型文件。 更为详细完整的示例可以参考实现一个图片分类应用

执行add_model.py脚本,生成tensor_add.mindir文件,该模型的输入为两个shape为[2,2]的二维Tensor,输出结果是两个输入Tensor之和。

部署Serving推理服务

启动Serving服务,以Add用例为例,需要如下文件列表:

test_dir
├── add/
│    └── servable_config.py
│    └── 1/
│        └── tensor_add.mindir
└── master_with_worker.py
  • master_with_worker.py为启动服务脚本文件。

  • add为模型文件夹,文件夹名即为模型名。

  • tensor_add.mindir为上一步网络生成的模型文件,放置在文件夹1下,1为版本号,不同的版本放置在不同的文件夹下,版本号需以纯数字串命名,默认配置下启动最大数值的版本号的模型文件。

  • servable_config.py模型配置文件,其定义了模型的处理函数,包括add_commonadd_cast两个方法,add_common定义了输入为两个普通float32类型的加法操作,add_cast定义输入类型为其他类型,经过输入类型转换float32后的加法操作。

模型配置文件内容如下:

import numpy as np
from mindspore_serving.worker import register


def add_trans_datatype(x1, x2):
    """define preprocess, this example has two input and two output"""
    return x1.astype(np.float32), x2.astype(np.float32)


# when with_batch_dim is set to False, only 2x2 add is supported
# when with_batch_dim is set to True(default), Nx2 add is supported, while N is viewed as batch
# float32 inputs/outputs
register.declare_servable(servable_file="tensor_add.mindir", model_format="MindIR", with_batch_dim=False)


# register add_common method in add
@register.register_method(output_names=["y"])
def add_common(x1, x2):  # only support float32 inputs
    """method add_common data flow definition, only call model inference"""
    y = register.call_servable(x1, x2)
    return y


# register add_cast method in add
@register.register_method(output_names=["y"])
def add_cast(x1, x2):
    """method add_cast data flow definition, only call preprocess and model inference"""
    x1, x2 = register.call_preprocess(add_trans_datatype, x1, x2)  # cast input to float32
    y = register.call_servable(x1, x2)
    return y

MindSpore Serving提供两种部署方式,轻量级部署和集群部署。轻量级部署master和worker在一个进程中,集群部署方式master和worker部署在不同的进程中。当只有一个worker节点时,用户可以考虑轻量级部署,即将master部署在这个worker所在进程中;当worker节点有多个,为了充分利用资源,可以考虑集群部署方式,选取一台机器作为master,管理所有的worker节点。用户可根据需要进行选择部署。

轻量级部署

服务端调用Python接口直接启动推理进程(master和worker共进程),客户端直接连接推理服务后下发推理任务。 执行master_with_worker.py,完成轻量级部署服务如下:

import os
from mindspore_serving import master
from mindspore_serving import worker

def start():
    servable_dir = os.path.abspath(".")
    worker.start_servable_in_master(servable_dir, "add", device_id=0)
    master.start_grpc_server("127.0.0.1", 5500)

if __name__ == "__main__":
    start()

当服务端打印日志Serving gRPC start success, listening on 0.0.0.0:5500时,表示Serving服务已加载推理模型完毕。

集群部署

服务端由master进程和worker进程组成,master用来管理集群内所有的worker节点,并进行推理任务的分发。部署方式如下:

部署master:

import os
from mindspore_serving import master

def start():
    servable_dir = os.path.abspath(".")
    master.start_grpc_server("127.0.0.1", 5500)
    master.start_master_server("127.0.0.1", 6500)
if __name__ == "__main__":
    start()

部署worker:

import os
from mindspore_serving import worker

def start():
    servable_dir = os.path.abspath(".")
    worker.start_servable(servable_dir, "add", device_id=0,
                          master_ip="127.0.0.1", master_port=6500,
                          worker_ip="127.0.0.1", worker_port=6600)

if __name__ == "__main__":
    start()

轻量级部署和集群部署启动worker所使用的接口存在差异,其中,轻量级部署使用start_servable_in_master接口启动worker,集群部署使用start_servable接口启动worker。

执行推理

客户端提供两种方式访问推理服务,一种是通过gRPC方式,一种是通过RESTful方式,本文以gRPC方式为例。 使用client.py,启动Python客户端。

import numpy as np
from mindspore_serving.client import Client


def run_add_common():
    """invoke servable add method add_common"""
    client = Client("localhost", 5500, "add", "add_common")
    instances = []

    # instance 1
    x1 = np.asarray([[1, 1], [1, 1]]).astype(np.float32)
    x2 = np.asarray([[1, 1], [1, 1]]).astype(np.float32)
    instances.append({"x1": x1, "x2": x2})

    # instance 2
    x1 = np.asarray([[2, 2], [2, 2]]).astype(np.float32)
    x2 = np.asarray([[2, 2], [2, 2]]).astype(np.float32)
    instances.append({"x1": x1, "x2": x2})

    # instance 3
    x1 = np.asarray([[3, 3], [3, 3]]).astype(np.float32)
    x2 = np.asarray([[3, 3], [3, 3]]).astype(np.float32)
    instances.append({"x1": x1, "x2": x2})

    result = client.infer(instances)
    print(result)


def run_add_cast():
    """invoke servable add method add_cast"""
    client = Client("localhost", 5500, "add", "add_cast")
    instances = []
    x1 = np.ones((2, 2), np.int32)
    x2 = np.ones((2, 2), np.int32)
    instances.append({"x1": x1, "x2": x2})
    result = client.infer(instances)
    print(result)


if __name__ == '__main__':
    run_add_common()
    run_add_cast()

使用mindspore_serving.client定义的Client类,客户端定义两个用例,分别调用模型的两个方法,run_add_common用例为三对float32类型数组相加操作,run_add_cast用例计算两个int32数组相加操作。执行后显示如下返回值,三对float32类型相加结果合集和一对int32类型的相加结果,说明Serving服务已正确执行Add网络的推理。

[{'y': array([[2. , 2.],
        [2.,  2.]], dtype=float32)},{'y': array([[4. , 4.],
        [4.,  4.]], dtype=float32)},{'y': array([[6. , 6.],
        [6.,  6.]], dtype=float32)}]
[{'y': array([[2. , 2.],
        [2.,  2.]], dtype=float32)}]