【Paddle系列】Paddle serving部署

41 阅读22分钟

1 前言

想要快速部署的就直接跳到部署示例,直接看一系列的配置肯定会云里雾里的!

2 关于PaddleServing

PaddleServing是飞桨官方推荐的服务化部署框架,围绕AI落地的最后一公里提供专业、可靠、易用的在线模型服务框架。训练完我们的模型之后,我们还想将模型做成服务化接口供外部调用,利用paddleserving即可部署PaddleOCR服务。

2.2流程

3 环境准备

本项目仅支持Python3.6/3.7/3.8/3.9,所有的与Python/Pip相关的操作都需要选择正确的Python版本。PaddleServing有三种安装方式,分别是docker镜像安装、原生系统环境安装和源码编译安装,三种方式选择其中一种即可。注意:请根据计算硬件、操作系统和软件驱动版本等环境差异,选择正确的安装程序。作为懒人租个服务器然后pip更快呢

3.1 使用docker部署Serving开发镜像

cpu版本和gpu版本二选一即可。使用docker部署的话,pip和python命令后要加上版本号,如pip3.6、python3.8。

  • cpu版本
docker pull registry.baidubce.com/paddlepaddle/serving:0.9.0-devel
docker run -p 9292:9292--name test_cpu -dit registry.baidubce.com/paddlepaddle/serving:0.9.0-devel bash
docker exec -it test_cpu bash
  • gpu版本
docker pull registry.baidubce.com/paddlepaddle/serving:0.9.0-cuda11.2-cudnn8-devel
nvidia-docker run -p 9292:9292 --name test_gpu -dit registry.baidubce.com/paddlepaddle/serving:0.9.0-cuda11.2-cudnn8-devel bash
nvidia-docker exec -it test_gpu bash

paddle serving使用9292端口传输数据,按需修改端口。

3.2 依赖库安装

  • cpu版本
git clone https://github.com/PaddlePaddle/Serving
cd Serving
pip3.8 install -r python/requirements.txt
pip3.8 install paddle-serving-client==0.9.0
pip3.8 install paddle-serving-app==0.9.0
pip3.8 install paddle-serving-server==0.9.0
pip3.8 install paddlepaddle==2.5.2
pip3.8 install paddlenlp==2.5.2
  • gpu版本

gpu版本安装比cpu的复杂,首先要根据gpu环境选择合适的版本。

post112=CUDA11.2+cuDNN8+TensorRT8(推荐)
post101=CUDA10.1+cuDNN7+TensorRT6
post102=CUDA10.2+cuDNN7+TensorRT6(与Paddle镜像一致)
post1028=CUDA10.2+cuDNN8+TensorRT7

确认好版本之后,再安装依赖。

git clone https://github.com/PaddlePaddle/Serving
cd Serving
pip3.8 install -r python/requirements.txt
pip3.8 install paddle-serving-client==0.9.0
pip3.8 install paddle-serving-app==0.9.0 --no-deps
# GPUServer,需要确认环境再选择执行哪一条,推荐使用CUDA11.2的包
pip3.8 install paddle-serving-server-gpu==0.9.0.post112
pip3.8 install paddlepaddle-gpu==2.5.1 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html
pip3.8 install paddlenlp==2.5.2

如果使用Linux安装,不使用docker安装

git clone https://github.com/PaddlePaddle/Serving
pip install shapely==1.8.0
pip install wheel>=0.34.0, <0.35.0
pip install setuptools>=44.1.0
pip install google>=2.0.3
pip install protobuf>=3.12.2
pip install grpcio-tools==1.33.2
pip install grpcio>=1.28.1
pip install func-timeout>=4.3.5
pip install pyyaml>=5.1
pip install flask>=1.1.2
pip install click==7.1.2
pip install itsdangerous==1.1.0
pip install Jinja2==2.11.3
pip install pyclipper==1.2.1
pip install MarkupSafe==1.1.1
pip install Werkzeug==1.0.1
pip install ujson>=2.0.3
pip install sentencepiece==0.1.96
pip install opencv-python==3.4.17.63
pip install pytest==7.0.1
pip install prometheus-client==0.12.0
pip install pillow==9.0.0
pip install av==8.0.3
pip install decord==0.4.2
pip install SimpleITK==1.2.4 -i https://mirrors.ustc.edu.cn/pypi/simple
pip install paddle-serving-client==0.9.0
pip install paddle-serving-app==0.9.0 --no-deps
pip install paddle-serving-server-gpu==0.9.0.post112
pip install paddlepaddle-gpu==2.5.2
pip install paddlenlp==2.8.1
pip install aistudio-sdk==0.2.6
pip install numpy==1.24.4

3.3 环境检查

PaddleServing提供了一键运行示例,检查PaddleServing环境是否安装正确。

python -m paddle_serving_server.serve check

启动后输入对应的命令来检查各部分是否编译成功,命令如下表所示。

命令描述
check_all检查PaddleInference、PipelineServing、C++Serving。只打印检测结果,不记录日志
check_pipeline检查PipelineServing,只打印检测结果,不记录日志
check_cpp检查C++Serving,只打印检测结果,不记录日志
check_inference检查PaddleInference是否安装正确,只打印检测结果,不记录日志
debug发生报错后,该命令将打印提示日志到屏幕,并记录详细日志文件
exit退出

4 模型开发

4.1 ocr模型训练

PaddleOCR训练文档

4.2 转换Serving

如果需要把训练好的模型部署至paddle自带的服务框架,需将inference模型转换为serving格式的文件。

python -m paddle_serving_client.convert \
--dirname '存放inference模型的文件夹名' \
--model_filename inference.pdmodel \	#需要转换的inference模型
--params_filename inference.pdiparams  \	#需要转换的模型参数
--serving_server serving_server \	#转换后的模型文件和配置文件的存储路径
--serving_client serving_client	#转换后的客户端配置文件存储路径

然后会生成serving_server、serving_client两个文件夹:

├──serving_client
│├──serving_client_conf.prototxt
│└──serving_client_conf.stream.prototxt
└──serving_server
├──inference.pdmodel
├──inference.pdiparams
├──serving_server_conf.prototxt
└──serving_server_conf.stream.prototxt

其中,serving_client_conf.prototxt和serving_server_conf.prototxt是PaddleServing的客户端和服务端需要加载的配置,serving_client_conf.stream.prototxt和serving_server_conf.stream.prototxt是配置文件的二进制形式。

打开文件【serving_server】-【serving_server_conf.prototxt】,找到fetch_var,记住fetch_var后面的name,这个在后面的配置文件需要用到。

  • feed_var:模型输入
  • fetch_var:模型输出
    • name:名称
    • alias_name:别名,与名称对应
    • is_lod_tensor:是否为lod
    • feed_type:数据类型
    • shape:数据维度

5 服务启动

PaddleServing为用户提供了两种推理服务,RPC服务和Web服务。

  • RPC服务是CS架构,用户使用客户端(Client)访问服务端(Server)获取推理结果,Client和Server之间的通信使用的是brpc框架来完成的。brpc是百度开源的、用C++编写的工业级RPC框架,具有高并发、低延时等特点,常用于搜索、存储、机器学习、广告、推荐等高性能系统,已经支持了包括百度在内上百万在线预估实例、上千个在线预估服务,稳定可靠。但是只适用于Linux操作系统,而且需要编写Client的代码。
  • Web服务是BS架构,用户可以使用浏览器或其他web应用通过http协议访问服务端。与RPC服务相比,该服务仅需向指定的url请求即可获得推理结果,不受操作系统的限制也无需编写Client代码。

5.1 RPC服务

5.1.1开启服务端

首先启动服务:

python -m paddle_serving_server.serve\
--model model_filename \
--thread 10 \
--port 9292 \
--gpu_ids 0,1 \	#指定gpu
--op_num 4	#指定并发数

如果服务需要部署多个模型时,则在对应的参数后面添加,例如:

python -m paddle_serving_server.serve \
--model model1, model2 \
--thread 10 \
--port 9292 \
--gpu_ids 0,1 1,2 \	#指定gpu
--op_num 4,8	#指定并发数

服务端无需做任何改造,即可支持RPC方式。

5.1.2客户端请求

客户端请求只需要四步:

1、创建一个Client对象

2、加载Client端的prototxt文件

3、准备请求数据

4、调用predict函数,请求预测服务

from paddle_serving_client import Client

client = Client()	#创建Client对象
client.load_client_config("serving_client/serving_client_conf.prototxt")	#加载Client端的prototxt文件
client.connect(["127.0.0.1:9292"])	#连接server端,端口要与启动命令的一致
data=[0.0137,-0.1136,0.2553,-0.0692,0.0582,-0.0727,
-0.1583,-0.0584,0.6283,0.4919,0.1856,0.0795,-0.0332]
result = client.predict(
    feed = {"x":np.array(data).reshape(1,13,1)},
    fetch = ["price"]
)
print(result)

其中feed是带有模型输入变量别名和值的dict,fetch从服务器返回的预测变量赋值。在训练过程中保存可服务模型时,被赋值的tensor名为"x"和"price"。

5.2 Web服务

5.2.1开启服务端

python -m paddle_serving_server.serve\
--model model_filename \
--thread 10 \
--port 9292 \
--gpu_ids 0,1 \	#指定gpu
--op_num 4	#指定并发数

5.2.2客户端请求

使用HTTPClient只需要四步:

1、创建一个HTTPClient对象

2、加载Client端的prototxt配置文件

3、调用connect函数

4、调用predict,通过http方式请求预测服务

curl -H "Content-Type:application/json" \
-XPOST http://127.0.0.1:9292/Service/prediction \
-d '{"feed":[{"x":[0.0137,-0.1136,0.2553,-0.0692,0.0582,-0.0727,-0.1583,-0.0584,0.6283,0.4919,0.1856,0.0795,-0.0332]}],"fetch":["price"]}'

其中Service字段和inference字段分别是Service服务名和rpc方法名,-d后面的是请求的数据体。

6 Pipeline框架

在许多深度学习框架中,模型服务化部署通常用于单模型的一键部署。但在AI工业大生产的背景下,端到端的单一深度学习模型不能解决复杂问题,多个深度学习模型组合使用是解决现实复杂问题的常规手段,如文字识别OCR服务至少需要检测和识别2种模型;视频理解服务一般需要视频抽帧、切词、音频处理、分类等多种模型组合实现。当前,通用多模型组合服务的设计和实现是非常复杂的,既要能实现复杂的模型拓扑关系,又要保证服务的高并发、高可用和易于开发和维护等。

Paddle Serving实现了一套通用的多模型组合服务编程框架Python Pipeline,不仅解决上述痛点,同时还能大幅提高GPU利用率,并易于开发和维护。

6.1 框架设计

Pipeline框架分为网络服务层和图执行引擎两部分,网络服务层处理多种网络协议请求和通用输入参数问题,图执行引擎层解决复杂拓扑关系。

6.1.1 网络服务层

转存失败,建议直接上传图片文件 网络服务层包括了gRPC-gateway和gRPCServer。gPRCgateway接收HTTP请求,打包成proto格式后转发给gRPCServer,一套处理程序可同时处理HTTP、gRPC两种类型请求。在支持多种模型的输入输出数据类型上,该框架使用统一的service.proto结构,具有更好的通用性。

Pipeline服务包装了继承于WebService类,以OCR示例为例,派生出OcrService类,get_pipeline_response函数内实现DAG拓扑关系,默认服务入口为read_op,函数返回的Op为最后一个处理节点,此处要求最后返回的Op必须唯一

所有服务和模型的所有配置信息在config.yml中记录,URL的name字段由OcrService初始化定义,run_service函数启动服务。

class OcrService(WebService):
    def get_pipeline_response(self,read_op):
        det_op = DetOp(name="det", input_ops=[read_op])
        rec_op = RecOp(name="rec", input_ops=[det_op])
        return ec_op
    
ocr_service = OcrService(name="ocr")
ocr_service.prepare_pipeline_config("config.yml")
ocr_service.run_service()

与网络框架相关的配置在config.yml中设置。其中worker_num表示框架主线程gRPC线程池工作线程数,可理解成网络同步线程并发数;rpc_port和http_port是服务端口,可同时开启,但不允许同时为空。

rpc_port: 9292
http_port: 18089
worker_num: 8

6.1.2 图执行引擎

图执行引擎(图论)的设计思路是基于有向无环图(DAG)实现多模型组合的复杂拓扑关系,有向无环图由单节点节点串联并联结构构成。可以理解成工作流,每次执行时按照固定的顺序执行节点的内容。

图执行引擎抽象归纳出两种数据结构Op节点和Channel有向边,构建一条异步流水线工作流。核心概念和设计思路如下:

  • Op节点:可理解成一个处理方法,可独立运行,独立设置并发度。每个Op节点的计算结果放入其绑定的Channel中。
  • Channel数据管道:可理解为一个单向缓冲队列。每个Channel只接收上游Op节点的计算输出,作为下游Op节点的输入。
  • 工作流:根据用户定义的节点依赖关系,图执行引擎自动生成有向无环图。每条用户请求到达图执行引擎时会生成一个唯一自增ID,通过这种唯一性绑定关系标记流水线中的不同请求。

Op的设计原则:

  • 单个Op默认的功能是根据输入的Channel数据,访问一个PaddleServing的单模型服务,并将结果存在输出的Channel
  • 单个Op可以支持用户自定义,包括preprocessprocesspostprocess三个函数都可以由用户继承和实现
  • 单个Op可以控制并发数,从而增加处理并发数
  • 单个Op可以获取多个不同RPC请求的数据,以实现Auto-Batching

其构造函数如下:

def __init__(
    name=None,	# 标识Op类型的字符串,必须全局唯一
    input_ops=[],	# 当前Op的所有前继Op的列表
    server_endpoints=[],
    fetch_list=[],	# 远程Serving Service的fetch列表
    client_config=None,	# 对应的Client端配置
    client_type=None,	# 可选择brpc、grpc或local_predictor。local_predictor不启动Serving服务,进程内预测
    concurrency=1,	# Op的并发数
    timeout=-1,	# process操作的超时时间
    retry=1,	# 超时重试次数
    batch_size=1,	# 批量处理大小
)

Channel的设计原则:

  • Channel是Op之间共享数据的数据结构,负责共享数据或者共享数据状态信息
  • Channel可以支持多个OP的输出存储在同一个Channel,同一个Channel中的数据可以被多个Op使用

6.1.3 服务日志

Pipeline服务日志在当前目录的PipelineServingLogs目录下,目录下有pipeline.log、pipeline.log.wf、pipeline.tracer。

在服务发生异常时,错误信息会记录在pipeline.log.wf日志中。打印tracer日志要求在config.yml的DAG属性中添加tracer配置。通常,Pipeline框架打印的日志会同时带上data_id和log_id。

Pipeline的日志模块在logger.py中定义,使用了logging.handlers.RotatingFileHandler支持磁盘日志文件的轮换。根据不同文件级别和日质量分别设置了maxBytes和backupCount,当即将超出预定大小时,将关闭旧文件并打开一个新文件用于输出。

6.1.4 自定义信息

6.1.4.1 自定义Web服务URL

在Web服务中自定义服务名称是常见操作,尤其是将已有服务迁移到新框架。URL中核心字段包括ip、port、name和method,根据最新部署的环境信息设置前2个字段,重点介绍如何设置name和method,框架提供默认的method是prediciton,如http://127.0.0.1:9999/ocr/prediction

框架有2处代码与此相关,分别是gRPCGateway的配置文件python/pipeline/gateway/proto/gateway.proto和服务启动文件web_server.py。

业务场景中通过设置name和验证method解决问题。以OCR示例为例,服务启动文件web_server.py通过类OcrService构造函数的name字段设置URL中name字段;

ocr_service=OcrService(name="ocr")
ocr_service.prepare_pipeline_config("config.yml")
ocr_service.run_service()

框架提供默认的method是prediciton,通过重载RequestOp::unpack_request_package来验证method。

def unpack_request_package(self, request):
    dict_data = {}
    log_id = None
    if request is None:
        _LOGGER.critical("request is None")
        raiseValueError("request is None")
    if request.method is not "prediction":
        _LOGGER.critical("request method error")
        raiseValueError("request method error")

在python/pipeline/gateway/proto/gateway.proto文件可以对name和method做严格限制,一般不需要修改,如需要特殊指定修改后,需要重新编译PaddleServing

6.1.4.2 自定义服务输入和输出结构

输入和输出结构包括proto中Request和Response结构,以及Op前后处理返回。

当默认proto结构不满足业务需求时,同时下面2个文件的proto的Request和Responsemessage结构,保持一致。

pipeline/gateway/proto/gateway.proto
pipeline/proto/pipeline_service.proto

修改后,需要重新编译。

6.1.4.3 自定义服务并发和模型配置

完整的配置信息可参考配置信息

6.1.4.4 自定义推理过程

推理Op提供3个外部函数接口:

  • preprocess(self, input_dicts)

    • 对从Channel中获取的数据进行处理,处理完的数据将作为process函数的输入。(该函数对一个sample进行处理)
  • process( self, feed_dict_list, typical_logid )

    • 基于PaddleServingClient进行RPC预测,处理完的数据将作为postprocess函数的输入。(该函数对一个batch进行处理)
  • postprocess( self, input_dicts, fetch_dict )

    • 处理预测结果,处理完的数据将被放入后继Channel中,以被后继Op获取。(该函数对一个sample进行处理)
  • init_op(self)

    • 用于加载资源(如字典等)
  • self.concurrency_idx

    • 当前进程(非线程)的并发数索引(不同种类的Op单独计算)

Op在一个运行周期中会依次执行preprocessprocesspostprocess三个操作(当不设置server_endpoints参数时,不执行process操作),用户可以对这三个函数进行重写,默认实现如下:

def preprocess(self, input_dicts):
    if len(input_dicts) != 1:
        raise NotImplementedError('this Op has multiple previous inputs.Please override this func.')
    (_, input_dict), = input_dicts.items()
    return input_dict

def process(self, feed_dict_list, typical_logid):
    err, err_info = ChannelData.check_batch_npdata(feed_dict_list)
    if err != 0:
        raise NotImplementedError("{}, Please over ride preprocess func.".format(err_info))
    call_result = self.client.predict(
        feed=feed_dict_list,
        fetch=self._fetch_names,
        log_id=typical_logid
    )
    if isinstance(self.client, MultiLangClient):
        if call_result is None or call_result["serving_status_code"] != 0:
            return None
        call_result.pop("serving_status_code")
    return call_result

def postprocess(self, input_dicts, fetch_dict):
    return fetch_dict
  • preprocess的参数是前继Channel中的数据input_dicts,该变量(作为一个sample)是一个以前继Op的name为Key,对应Op的输出为Value的字典。

  • process的参数是PaddleServingClient预测接口的输入变量fetch_dict_list(preprocess函数的返回值的列表),该变量(作为一个batch)是一个列表,列表中的元素为以feed_name为Key,对应ndarray格式的数据为Value的字典。typical_logid作为向PaddleServingService穿透的logid。

  • postprocess的参数是input_dicts和fetch_dict,input_dicts与preprocess的参数一致,fetch_dict(作为一个sample)是process函数的返回batch中的一个sample(如果没有执行process,则该值为preprocess的返回值)。

用户还可以对init_op函数进行重写,已加载自定义的一些资源(比如字典等),默认实现如下:

def init_op(self):
    pass

RequestOpResponseOp是Python Pipeline的中2个特殊Op,分别是用分解RPC数据加入到图执行引擎中,和拿到图执行引擎的预测结果并打包RPC数据到客户端。RequestOp类的设计如下所示,核心是在unpack_request_package函数中解析请求数据,因此,当修改Request结构后重写此函数实现全新的解包处理。

接口说明
init_op(self)OP初始化,设置默认名称@DAGExecutor
unpack_request_package(self, request)解析请求数据
class RequestOp(Op):
    def __init__(self):
        # PipelineService.name="@DAGExecutor"
        super(RequestOp, self).**init**(name="@DAGExecutor", input_ops=[])
        # init op
        try:
            self.init_op()
        except Exception as e:
            _LOGGER.critical("Op(Request) Failed to init:{}".format(e))
            os.\_exit(-1)

    def unpack_request_package(self, request):
        dict_data = {}
        log_id = None
        if request is None:
            _LOGGER.critical("request is None")
            raise ValueError("request is None")

        for idx, key in enumerate(request.key):
            dict_data[key] = request.value[idx]
            log_id = request.logid
            _LOGGER.info(
                "RequestOp unpack one request.log_id:{},clientip:{}name:{},method:{}".format(log_id,request.clientip, request.name, request.method)
            )
        return dict_data, log_id, None, ""

ResponseOp类的设计如下所示,核心是在pack_response_package中打包返回结构,因此修改Response结构后重写此函数实现全新的打包格式。

接口说明
init_op(self)OP初始化,设置默认名称@DAGExecutor
pack_response_package(self, channeldata)处理接收的RPC数据
class ResponseOp(Op):
    def __init__(self, input_ops):
        super(ResponseOp,self).__init__(
            name="@DAGExecutor",
            input_ops=input_ops
        )
        # init op
        try:
            self.init_op()
        except Exception as e:
            _LOGGER.critical("Op(ResponseOp) Failed to init:{}".format(e,exc_info=True))
            os._exit(-1)

    def pack_response_package(self, channeldata):
        resp = pipeline_service_pb2.Response()
        error_code = channeldata.error_code
        error_info = ""
        ...
        # pack results
        if error_code is None:
            error_code = 0
            resp.err_no = error_code
            resp.err_msg = error_info
            return resp
5.1.4.5 自定义业务错误类型

用户可根据业务场景自定义错误码,继承ProductErrCode,在Oppreprocesspostprocess中返回列表中返回,下一阶段处理会根据自定义错误码跳过后置OP处理。详情请见官方文档,因为用的较少此处不在赘述。

6.2 服务启动与推理

从设计上,Python Pipeline 框架实现轻量级的服务化部署,提供了丰富的核心功能,既能满足服务基本使用,又能满足特性需求。

如果模型处理过程包含一个以上的模型推理环节(例如OCR一般需要det+rec两个环节),此时有两种做法可以满足您的需求:

  • 启动两个Serving服务(例如Serving-det,Serving-rec)

在您的Client中,读入数据——>det前处理——>调用Serving-det预测——>det后处理——>rec前处理——>调用Serving-rec预测——>rec后处理——>输出结果。

  • 通过修改代码,自定义模型预测行为(自定义OP),自定义服务处理的流程(自定义DAG),将多个模型的组合处理过程

(上述的det前处理——>调用Serving-det预测——>det后处理——>rec前处理——>调用Serving-rec预测——>rec后处理)集成在一个Serving服务中。此时,在您的Client中,读入数据——>调用集成后的Serving——>输出结果。

6.2.1 启动两个Serving服务

启动时要要同时启动serving服务与client端。

  • Server端启动

要实现一个服务启动两个模型串联,只需要在--model后依次按顺序传入模型文件夹的相对路径,且需要在--op后依次传入自定义的Op类名称,其中--model后面的模型与--op后面的类名称的顺序需要对应。假设已经定义好了两个Op分别为GeneralDetOp和GeneralRecOp,则脚本代码如下:

#一个服务启动多模型串联
python3 -m paddle_serving_server.serve  \
    --model ocr_det_model ocr\_rec\_model  \
    --op GeneralDetOp GeneralRecOp   \	#多模型串联ocr_det_model对应GeneralDetectionOpocr_rec_model对应GeneralRecOp
    --port 9292
  • Client端调用

Client端的调用也需要传入两个Client端的proto文件或文件夹的路径,假定文件ocr_client.py为调用脚本,此时Client调用如下:

#一个服务启动多模型串联
python3 ocr_client.py ocr_det_client ocr_rec_client
#ocr_det_client为第一个模型的Client端proto文件夹的相对路径
#ocr_rec_client为第二个模型的Client端proto文件夹的相对路径

此时,对于Server端而言,输入的数据的格式与第一个模型的Client端proto格式定义的一致,输出的数据格式与最后一个模型的Client端proto文件一致。

6.2.2 Pipeline自定义模型op实现本地与远程推理

本地推理是指在服务所在机器环境下开启多进程推理,而远程推理是指本地服务请求远程 C++ Serving 推理服务。

本地推理的优势是实现简单,一般本地处理相比于远程推理耗时更低。而远程推理的优势是可实现 Python Pipeline 较难实现的功能,如部署加密模型,大模型推理。

使用pipeline自定义op需要绑定配置,详细的配置项参考官方文档。

  • 本地推理
op:
    uci:	# op名字自定义
        concurrency: 10	# 并发数,is_thread_op=True时,为线程并发;否则为进程并发
        local_service_conf:
        model_config: ocr_det_model	# 写到serving_server文件夹
        device_type: 0
        devices: ""
        client_type: local_predictor
        fetch_list: ["result"]
  • 远程推理

由于配置项较多,仅重点介绍部分核心选项的使用。

op:
    bow:
    concurrency: 1	# cpu环境适当降低并发数
    client_type: brpc
    retry: 1	# Serving交互重试次数,默认不重试
    timeout: 3000	# Serving交互超时时间, 单位ms
    server_endpoints: ["127.0.0.1:9393"]	# Serving IPs
    # client端配置,写到serving_client_conf.prototxt
    client_config: "imdb_bow_client_conf/serving_client_conf.prototxt"
    fetch_list: ["prediction"]	# fetch结果列表,以client_config中fetch_var的alias_name为准

#rpc端口,rpc_port和http_port不允许同时为空。当rpc_port为空且http_port不为空时,会自动将rpc_port设置为http_port+1
rpc_port: 18090
#http端口,rpc_port和http_port不允许同时为空。当rpc_port可用且http_port为空时,不自动生成http_port
http_port: 9999
worker_num: 20	# 最大并发数
build_dag_each_worker: False	 # False,框架在进程内创建一条DAG;True,框架会每个进程内创建多个独立的DAG

#有向无环图级别的选项
dag:
    # op资源类型,True,为线程模型;False,为进程模型
    is_thread_op: False
    #重试次数
    retry: 1
    #使用性能分析,True,生成Timeline性能数据,对性能有一定影响;False为不使用
    use_profile: False
    #统计各个阶段耗时、Channel在PipelineServingLogs/pipeline.tracer
    tracer:
    #每次记录的间隔,单位:秒
    interval_s: 10

op:
det:
    concurrency: 6
    # 当op配置没有server_endpoints时,从loca_service_conf读取本地服务配置
    local_service_conf:
    # client类型,包括brpc,grpc和local_predictor,local_predictor不启动Serving服务,进程内预测
    client_type: local_predictor
    model_config: ocr_det_model	# 写到serving_server文件夹
    # Fetch结果列表,以client_config中fetch_var的alias_name为准
    fetch_list: ["save_infer_model/scale_0.tmp_1"]
    device_type: 0	# 设备类型,0=cpu,1=gpu,2=tensorRT
    devices: ""	# 计算硬件ID,当devices为""或不写时为CPU预测
    thread_num: 2	# 线程数

rec:
...(同上)

配置文件中要定义op的相关属性,每个op处理和图结构定义在web_service.py程序中,假设实现了DetOp和RecOp2个OP:

# DetOp对应配置文件Config.yml中detop
class DetOp(Op):
    def init_op(self):
    def preprocess(self, input_dicts, data_id, log_id):
    def postprocess(self, input_dicts, fetch_dict, data_id, log_id):

# RecOp对应配置文件Config.yml中recop
class RecOp(Op):
    def init_op(self):
    def preprocess(self, input_dicts, data_id, log_id):
    def postprocess(self, input_dicts, fetch_dict, data_id, log_id):

7 部署示例

通过6个步骤操作即可实现OCR实例部署:

本文仅展示使用pipeline部署的服务。

假定现在有一个身份证ocr识别项目,需要识别出姓名、性别、出生日期、住址和身份证件号。详细的流程如下图所示:

7.1 项目架构

OCRProject/
    - model/ # 存放serving模型
        - det_client
            - serving_client_conf.prototxt
            - serving_client_conf.stream.prototxt
        - det_server
            - inference.pdiparams
            - inference.pdmodel
            - serving_server_conf.prototxt
            - serving_server_conf.stream.prototxt
        - rec_client(如有)
        - rec_server(如有)
        - ser_client
        - ser_server
    - op/  # 自定义op
    - remote_op/
    - ocr_dict/	# ocr字典
        - class_list.txt	# 存放ser模型的字段
    - process/ # 流程
    - train_data/  # 训练数据
    - config.yml	# 配置文件
    - web_service.py
    - web_define.py
    - pipeline_rpc_client.py

7.2 获取模型

按需训练det、rec、ser模型,并转换成serving格式的模型。训练方法见文章

7.2 保存Serving部署的模型参数

将inference模型转换为serving格式的文件。

python -m paddle_serving_client.convert  \
    --dirname '存放inference模型的文件夹名' \
    --model_filename inference.pdmodel \	#需要转换的inference模型
    --params_filename inference.pdiparams  \	#需要转换的模型参数
    --serving_server serving_server \	#转换后的模型文件和配置文件的存储路径
    --serving_client serving_client	#转换后的客户端配置文件存储路径

然后会生成serving_server、serving_client两个文件夹:

├──serving_client
    │├──serving_client_conf.prototxt
    │└──serving_client_conf.stream.prototxt
└──serving_server
    ├──inference.pdmodel
    ├──inference.pdiparams
    ├──serving_server_conf.prototxt
    └──serving_server_conf.stream.prototxt

打开文件【serving_server】-【serving_server_conf.prototxt】,找到fetch_var,记住fetch_var后面的name,这个在后面的配置文件需要用到。

7.3修改config配置

由于OP配置中存在大量重复配置,所以会使用yml的变量对配置进行简化处理。&为变量定义,*引用变量

7.4代码与配置信息绑定

7.5启动服务与验证

8 常见问题

  1. 依赖库av安装失败

原因:av包版本与FFmpeg不兼容,可以试试:重新编译FFmpeg

wget <https://ffmpeg.org/releases/ffmpeg-4.4.tar.gz>
tar -xzf ffmpeg-4.4.tar.gz
cd ffmpeg-4.4

apt-get install -y build-essential yasm pkg-config

./configure --enable-shared --prefix=/usr/local
make -j\$(nproc)
make install

ldconfig

/usr/local/bin/ffmpeg -version

#再重新安装
pip cache purge
pip install av==8.0.3 --no-cache-dir

出现安装依赖报错No module named '_distutils_hack',先安装setuptools库为57.5.0版本再安装av库。

  1. opencv-python==3.4.17.61安装失败

直接安装3.4.17.63版本,然后跳过61版本的安装

  1. 安装SimplrITK库卡住
pip install SimpleITK==1.2.4 -i <https://mirrors.ustc.edu.cn/pypi/simple>
  1. 安装过程问题

如果出现123的问题的话,后续可能会出现安装error,忽略第一个opencv的即可,其他几个库直接pip install 就行