From 13bffa43827a299d1df9fefd8591aa576128395d Mon Sep 17 00:00:00 2001 From: wangguibao Date: Wed, 6 Mar 2019 16:47:54 +0800 Subject: [PATCH] Documentation Change-Id: Ibd7b47fddcb75236a5f1624c32c4a674e87ed633 --- README.md | 8 +- doc/CLIENT_CONFIGURE.md | 10 +- doc/CREATING.md | 20 +-- doc/DESIGN.md | 331 +++++++++++++++++++++++++++++++++++++++- doc/FAQ.md | 7 + doc/INSTALL.md | 39 +++++ 6 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 doc/FAQ.md diff --git a/README.md b/README.md index 17478804..3dc45b83 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -[TOC] # 概述 PaddlePaddle是公司开源的机器学习框架,广泛支持各种深度学习模型的定制化开发; Paddle serving是Paddle的在线预测部分,与Paddle模型训练环节无缝衔接,提供机器学习预测云服务。 @@ -47,9 +46,4 @@ Paddle serving框架为策略工程师提供以下三层面的功能性扩展: # 设计文档 -# FAQ -1. 如何修改端口配置? -- 使用该框架搭建的服务需要申请一个端口,可以通过以下方式修改端口号: -- 如果在inferservice_file里指定了port:xxx,那么就去申请该端口号; -- 否则,如果在gflags.conf里指定了--port:xxx,那就去申请该端口号; -- 否则,使用程序里指定的默认端口号:8010。 +[设计文档](doc/DESIGN.md) diff --git a/doc/CLIENT_CONFIGURE.md b/doc/CLIENT_CONFIGURE.md index 25beb8d1..78c3245d 100644 --- a/doc/CLIENT_CONFIGURE.md +++ b/doc/CLIENT_CONFIGURE.md @@ -2,7 +2,7 @@ Paddle-serving C++ client SDK主配置文件为conf/predictors.prototxt。其中一个示例如下: -## Sample conf +## 1. Sample conf ```shell default_variant_conf { @@ -65,14 +65,14 @@ predictors { ``` -## 名词解释 +## 2. 名词解释 - 预测服务 (Predictor):对一个Paddle预测服务的封装 - 端点(Endpoit):对一个预测需求的逻辑抽象,通常包含一到多个服务变体,以方便多版本模型管理; - 变体(Variant):一套同质化的Paddle-serving集群服务,每个实例起一个Paddle-serving进程; -## 配置项解释 +## 3. 配置项解释 -### default_variant_conf +### 3.1 default_variant_conf ```shell default_variant_conf { @@ -110,7 +110,7 @@ compress_type: 0-None, 1-Snappy, 2-gzip, 3-zlib, 4-lz4, see [BRPC DOC: compress_ protocol: Maybe baidu_std/http/h2/h2:grpc/thrift/memcache/redis... see [BRPC DOC: protocol](https://github.com/apache/incubator-brpc/blob/master/docs/cn/client.md#%E5%8D%8F%E8%AE%AE) -### Predictors +### 3.2 Predictors 可以为客户端配置多个predictor,每个predictor代表一个要访问的预测服务 diff --git a/doc/CREATING.md b/doc/CREATING.md index 21bbedf9..e24eeca4 100644 --- a/doc/CREATING.md +++ b/doc/CREATING.md @@ -13,7 +13,7 @@ paddle-serving已经提供了一个基于ResNet的模型预测服务,按照INS ### 2.1 定义预测接口 -** 添加文件:serving/proto/image_class.proto ** +**添加文件:serving/proto/image_class.proto** Paddle-serving服务端与客户端通过brpc进行通信,通信协议和格式可以自定,我们选择baidu_std协议。这是一种以protobuf为基本数据交换格式的协议,其说明可参考[BRPC文档: baidu_std](https://github.com/apache/incubator-brpc/blob/master/docs/cn/baidu_std.md)。 @@ -64,7 +64,7 @@ service ImageClassifyService { #### 2.2.1 定制Op算子 -** 在serving/op/目录下添加reader_op.cpp, classify_op.cpp, write_json_op.cpp ** +**在serving/op/目录下添加reader_op.cpp, classify_op.cpp, write_json_op.cpp** - 预处理算子(ReaderOp, serving/op/reader_op.cpp):从Request中读取图像字节流,通过opencv解码,填充tensor对象并输出到channel; - 预测调用算子(ClassifyOp, serving/op/classify_op.cpp):从ImageReaderOp的channel获得输入tensor,临时申请输出tensor,调用ModelToolkit进行预测,并将输出tensor写入channel @@ -79,7 +79,7 @@ service ImageClassifyService { - 配置文件示例: -** 添加文件 serving/conf/service.prototxt ** +**添加文件 serving/conf/service.prototxt** ```shell services { @@ -88,7 +88,7 @@ services { } ``` -** 添加文件 serving/conf/workflow.prototxt ** +**添加文件 serving/conf/workflow.prototxt** ```shell workflows { @@ -119,14 +119,14 @@ workflows { 以下配置文件为模型加载配置 -** 添加文件 serving/conf/resource.prototxt ** +**添加文件 serving/conf/resource.prototxt** ```shell model_manager_path: ./conf model_manager_file: model_toolkit.prototxt ``` -** 添加文件 serving/conf/model_toolkit.prototxt ** +**添加文件 serving/conf/model_toolkit.prototxt** ```shell engines { @@ -221,7 +221,7 @@ target_link_libraries(serving opencv_imgcodecs ### 3.1 定义预测接口 -** 在sdk-cpp/proto添加image_class.proto ** +**在sdk-cpp/proto添加image_class.proto** 与serving端预测接口protobuf文件基本一致,只要将`generate_impl=true`改为`generate_stub=true` @@ -266,7 +266,7 @@ class Predictor { #### 3.2.1 请求逻辑 -** 增加sdk-cpp/demo/ximage.cpp ** +**增加sdk-cpp/demo/ximage.cpp** ```c++ // 进程级初始化 @@ -323,7 +323,7 @@ $ protoc --cpp_out=/path/to/paddle-serving/build/serving/ --pdcodegen_out=/path/ 其中 `pdcodegen`是由predictor/src/pdcodegen.cpp编译成的protobuf编译插件, --proto_path用来指定去哪里寻找`import`语句需要的protobuf文件 -** NOTE ** +**NOTE** 上述protoc命令在paddle-serving编译系统中被封装成一个CMake函数了,在cmake/generic.cmake::PROTOBUF_GENERATE_SERVING_CPP CMakeLists.txt中调用函数的方法为: ```shell @@ -347,7 +347,7 @@ target_link_libraries(ximage -Wl,--whole-archive sdk-cpp ### 3.4 连接配置 -** 增加配置文件sdk/conf/predictors.prototxt ** +**增加配置文件sdk/conf/predictors.prototxt** ```shell ## 默认配置共享 diff --git a/doc/DESIGN.md b/doc/DESIGN.md index d4b93dc3..76ceac5a 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -1,3 +1,330 @@ -# 设计文档 +# Paddle-Serving设计方案 -# 项目背景 +## 1. 项目背景 + +PaddlePaddle是公司开源的机器学习框架,广泛支持各种深度学习模型的定制化开发; Paddle serving是Paddle的在线预测部分,与Paddle模型训练环节无缝衔接,提供机器学习预测云服务。本文将从模型、服务、接入等层面,自底向上描述Paddle-serving设计方案。 + +1. 模型是Paddle-Serving预测的核心,包括模型数据和推理计算的管理; +2. 预测框架封装模型推理计算,对外提供RPC接口,对接不同上游; +3. 预测服务SDK提供一套接入框架 + +最终形成一套完整的serving解决方案。 + +## 2. 名词解释 + +- baidu-rpc 百度官方开源RPC框架,支持多种常见通信协议,提供基于protobuf的自定义接口体验 +- Variant Paddle-serving架构对一个最小预测集群的抽象,其特点是内部所有实例(副本)完全同质,逻辑上对应一个model的一个固定版本 +- Endpoint 多个Variant组成一个Endpoint,逻辑上看,Endpoint代表一个model,Endpoint内部的Variant代表不同的版本 +- OP PaddlePaddle用来封装一种数值计算的算子,Paddle-Serving用来表示一种基础的业务操作算子,核心接口是inference。OP通过配置其依赖的上游OP,将多个OP串联成一个workflow +- Channel 一个OP所有请求级中间数据的抽象;OP之间通过Channel进行数据交互 +- Bus 对一个线程中所有channel的管理,以及根据DAG之间的DAG依赖图对OP和Channel两个集合间的访问关系进行调度 +- Stage Workflow按照DAG描述的拓扑图中,属于同一个环节且可并行执行的OP集合 +- Workflow 按照DAG描述的拓扑,有序执行每个OP的inference接口 +- App 对workflow的逻辑封装,框架加载每套workflow配置时,会生成一个App实例,可并行管理多个App +- Service 对多个App组成的复杂调度拓扑的逻辑封装,同一个service内的App之间可能存在依赖关系;服务框架可加载多套service配置,并行管理多套service,一次请求只能访问一个service。 + +## 3. Paddle-Serving总体框架 + +![Paddle-Serging总体框图](https://paddle-serving.bj.bcebos.com/doc/framework.png) + +**模型管理框架**:对接多种机器学习平台的模型文件,向上提供统一的inference接口 +**业务调度框架**:对各种不同预测模型的计算逻辑进行抽象,提供通用的DAG调度框架,通过DAG图串联不同的算子,共同完成一次预测服务。该抽象模型使用户可以方便的实现自己的计算逻辑,同时便于算子共用。(用户搭建自己的预测服务,很大一部分工作是搭建DAG和提供算子的实现) +**PredictService**:对外部提供的预测服务接口封装。通过protobuf定义与客户端的通信字段。 + +### 3.1 模型管理框架 + +模型管理框架负责管理机器学习框架训练出来的模型,总体可抽象成模型加载、模型数据和模型推理等3个层次。 + +#### 模型加载 + +将模型从磁盘加载到内存,支持多版本、热加载、增量更新等功能 + +#### 模型数据 + +模型在内存中的数据结构,集成fluid预测lib + +#### inferencer + +向上为预测服务提供统一的预测接口 + +```C++ +class FluidFamilyCore { + virtual bool Run(const void* in_data, void* out_data); + virtual int create(const std::string& data_path); + virtual int clone(void* origin_core); +}; +``` + +### 3.2 业务调度框架 + +#### 3.2.1 预测服务Service + +参考TF框架的模型计算的抽象思想,将业务逻辑抽象成DAG图,由配置驱动,生成workflow,跳过C++代码编译。业务的每个具体步骤,对应一个具体的OP,OP可配置自己依赖的上游OP。OP之间消息传递统一由线程级Bus和channel机制实现。例如,一个简单的预测服务的服务过程,可以抽象成读请求数据->调用预测接口->写回预测结果等3个步骤,相应的实现到3个OP: ReaderOp->ClassifyOp->WriteOp + +![预测服务Service](https://paddle-serving.bj.bcebos.com/doc/predict-service.png) + +关于OP之间的依赖关系,以及通过OP组建workflow,可以参考[从零开始写一个预测服务](CREATING.md)的相关章节 + +#### 3.2.2 Paddle-Serving的多服务机制 + +![Paddle-Serving的多服务机制](https://paddle-serving.bj.bcebos.com/doc/multi-service.png) + +Paddle-Serving实例可以同时加载多个模型,每个模型用一个Service(以及其所配置的workflow)承接服务。可以参考[Demo例子中的service配置文件](../serving/conf/service.prototxt)了解如何为serving实例配置多个service + +#### 3.2.3 业务调度层级关系 + +从客户端看,一个Paddle-Serving service从顶向下可分为Service, Endpoint, Variant等3个层级 + +![调用层级关系](https://paddle-serving.bj.bcebos.com/doc/multi-variants.png) + +一个Service对应一个预测模型,模型下有1个endpoint。模型的不同版本,通过endpoint下多个variant概念实现: +同一个模型预测服务,可以配置多个variant,每个variant有自己的下游IP列表。客户端代码可以对各个variant配置相对权重,以达到调节流量比例的关系(参考[客户端配置](CLIENT_CONFIGURE.md)第3.2节中关于variant_weight_list的说明)。 + + +## 4. 用户接口 + +在满足一定的接口规范前提下,服务框架不对用户数据字段做任何约束,以应对各种预测服务的不同业务接口。Baidu-rpc继承了Protobuf serice的接口,用户按照Protobuf语法规范描述Request和Response业务接口。Paddle-Serving基于Baidu-rpc框架搭建,默认支持该特性。 + +无论通信协议如何变化,框架只需确保Client和Server间通信协议和业务数据两种信息的格式同步,即可保证正常通信。这些信息又可细分如下: + +- 协议:Server和Client之间事先约定的、确保相互识别数据格式的包头信息。Paddle-Serving用Protobuf作为基础通信格式 +- 数据:用来描述Request和Response的接口,例如待预测样本数据,和预测返回的打分。包括: + - 数据字段:请求包Request和返回包Response两种数据结构包含的字段定义 + - 描述接口:跟协议接口类似,默认支持Protobuf + +### 4.1 数据压缩方法 + +Baidu-rpc内置了snappy, gzip, zlib等数据压缩方法,可在配置文件中配置(参考[客户端配置](CLIENT_CONFIGURE.md)第3.1节关于compress_type的介绍) + +### 4.2 C++ SDK API接口 + +```C++ +class PredictorApi { + public: + int create(const char* path, const char* file); + int thrd_initialize(); + int thrd_clear(); + int thrd_finalize(); + void destroy(); + + Predictor* fetch_predictor(std::string ep_name); + int free_predictor(Predictor* predictor); +}; + +class Predictor { + public: + // synchronize interface + virtual int inference(google::protobuf::Message* req, + google::protobuf::Message* res) = 0; + + // asynchronize interface + virtual int inference(google::protobuf::Message* req, + google::protobuf::Message* res, + DoneType done, + brpc::CallId* cid = NULL) = 0; + + // synchronize interface + virtual int debug(google::protobuf::Message* req, + google::protobuf::Message* res, + butil::IOBufBuilder* debug_os) = 0; +}; + +``` + +### 4.3 OP相关接口 + +```C++ +class Op { + // ------Getters for Channel/Data/Message of dependent OP----- + + // Get the Channel object of dependent OP + Channel* mutable_depend_channel(const std::string& op); + + // Get the Channel object of dependent OP + const Channel* get_depend_channel(const std::string& op) const; + + template + T* mutable_depend_argument(const std::string& op); + + template + const T* get_depend_argument(const std::string& op) const; + + // -----Getters for Channel/Data/Message of current OP---- + + // Get pointer to the progobuf message of current OP + google::protobuf::Message* mutable_message(); + + // Get pointer to the protobuf message of current OP + const google::protobuf::Message* get_message() const; + + // Get the template class data object of current OP + template + T* mutable_data(); + + // Get the template class data object of current OP + template + const T* get_data() const; + + // ---------------- Other base class members ---------------- + + int init(Bus* bus, + Dag* dag, + uint32_t id, + const std::string& name, + const std::string& type, + void* conf); + + int deinit(); + + + int process(bool debug); + + // Get the input object + const google::protobuf::Message* get_request_message(); + + const std::string& type() const; + + uint32_t id() const; + + // ------------------ OP Interface ------------------- + + // Get the derived Channel object of current OP + virtual Channel* mutable_channel() = 0; + + // Get the derived Channel object of current OP + virtual const Channel* get_channel() const = 0; + + // Release the derived Channel object of current OP + virtual int release_channel() = 0; + + // Inference interface + virtual int inference() = 0; + + // ------------------ Conf Interface ------------------- + virtual void* create_config(const configure::DAGNode& conf) { return NULL; } + + virtual void delete_config(void* conf) {} + + virtual void set_config(void* conf) { return; } + + // ------------------ Metric Interface ------------------- + virtual void regist_metric() { return; } +}; + +``` + +### 4.4 框架相关接口 + +Service + +```C++ +class InferService { + public: + static const char* tag() { return "service"; } + int init(const configure::InferService& conf); + int deinit() { return 0; } + int reload(); + const std::string& name() const; + const std::string& full_name() const { return _infer_service_format; } + + // Execute each workflow serially + virtual int inference(const google::protobuf::Message* request, + google::protobuf::Message* response, + butil::IOBufBuilder* debug_os = NULL); + + int debug(const google::protobuf::Message* request, + google::protobuf::Message* response, + butil::IOBufBuilder* debug_os); + +}; + +class ParallelInferService : public InferService { + public: + // Execute workflows in parallel + int inference(const google::protobuf::Message* request, + google::protobuf::Message* response, + butil::IOBufBuilder* debug_os) { + return 0; + } +}; +``` +ServerManager + +```C++ +class ServerManager { + public: + typedef google::protobuf::Service Service; + ServerManager(); + + static ServerManager& instance() { + static ServerManager server; + return server; + } + static bool reload_starting() { return _s_reload_starting; } + static void stop_reloader() { _s_reload_starting = false; } + int add_service_by_format(const std::string& format); + int start_and_wait(); +}; +``` + +DAG + +```C++ +class Dag { + public: + EdgeMode parse_mode(std::string& mode); // NOLINT + + int init(const char* path, const char* file, const std::string& name); + + int init(const configure::Workflow& conf, const std::string& name); + + int deinit(); + + uint32_t nodes_size(); + + const DagNode* node_by_id(uint32_t id); + + const DagNode* node_by_id(uint32_t id) const; + + const DagNode* node_by_name(std::string& name); // NOLINT + + const DagNode* node_by_name(const std::string& name) const; + + uint32_t stage_size(); + + const DagStage* stage_by_index(uint32_t index); + + const std::string& name() const { return _dag_name; } + + const std::string& full_name() const { return _dag_name; } + + void regist_metric(const std::string& service_name); +}; +``` + +Workflow + +```C++ +class Workflow { + public: + Workflow() {} + static const char* tag() { return "workflow"; } + + // Each workflow object corresponds to an independent + // configure file, so you can share the object between + // different apps. + int init(const configure::Workflow& conf); + + DagView* fetch_dag_view(const std::string& service_name); + + int deinit() { return 0; } + + void return_dag_view(DagView* view); + + int reload(); + + const std::string& name() { return _name; } + + const std::string& full_name() { return _name; } +}; +``` diff --git a/doc/FAQ.md b/doc/FAQ.md new file mode 100644 index 00000000..a48e5c4d --- /dev/null +++ b/doc/FAQ.md @@ -0,0 +1,7 @@ +# FAQ +## 1. 如何修改端口配置? +使用该框架搭建的服务需要申请一个端口,可以通过以下方式修改端口号: + +- 如果在inferservice_file里指定了port:xxx,那么就去申请该端口号; +- 否则,如果在gflags.conf里指定了--port:xxx,那就去申请该端口号; +- 否则,使用程序里指定的默认端口号:8010。 diff --git a/doc/INSTALL.md b/doc/INSTALL.md index c03f98cb..3c2d1fe7 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -3,7 +3,9 @@ ## 系统需求 OS: Linux + CMake: 3.2 + python ## 编译 @@ -16,3 +18,40 @@ $ cmake .. $ make -j4 $ make install ``` + +`make install`将把目标产出放在/path/to/paddle-serving/build/output/目录下,目录结构: + +``` +. +|-- bin # Paddle-serving protobuf编译插件pdcodegen所在目录 +|-- demo # demo总目录 +| |-- client +| | |-- dense_format # dense_format客户端 +| | | |-- bin # bin/dense_format是dense_format客户端bin +| | | `-- conf +| | |-- echo # echo服务客户端 +| | | |-- bin # bin/echo是echo客户端bin +| | | \-- conf +| | |-- image_classification # image_classification服务客户端 +| | | |-- bin # bin/ximage是image_classification客户端bin +| | | |-- conf +| | | |-- data +| | | `-- images +| | |-- int64tensor_format # int64tensor_format服务客户端 +| | | |-- bin # bin/int64tensor_format是客户端bin +| | | `-- conf +| | `-- sparse_format # sparse_format客户端 +| | |-- bin # bin/sparse_format是客户端bin +| | `-- conf +| `-- serving # serving端,同时提供echo/dense_format/sparse_format/int64tensor_format/image_class等5种服务 +| |-- bin # bin/serving是serving端可执行bin +| |-- conf # 配置文件目录 +| |-- data +| | `-- model +| | `-- paddle +| | `-- fluid +| | `-- SE_ResNeXt50_32x4d # image_classification模型 +`-- lib # Paddle-serving产出的静态库文件: libpdseving.a, libsdk-cpp.a, libconfigure.a, libfluid_cpu_engine.a +``` + +如要编写新的预测服务,请参考[从零开始写一个预测服务](CREATING.md) -- GitLab