From 97ac2a5723abd53557435092c387eadbfb32ff8e Mon Sep 17 00:00:00 2001 From: wangjiawei04 Date: Wed, 18 Mar 2020 15:26:17 +0800 Subject: [PATCH] add criteo_with_cube --- core/configure/proto/server_configure.proto | 7 +- core/general-server/CMakeLists.txt | 1 + .../op/general_dist_kv_infer_op.cpp | 155 +++++++++++++ .../op/general_dist_kv_infer_op.h | 46 ++++ core/predictor/common/CMakeLists.txt | 7 +- core/predictor/common/seq_file.cpp | 83 +++++++ core/predictor/common/seq_file.h | 52 +++++ core/predictor/common/seq_generator.cpp | 116 ++++++++++ core/predictor/framework/resource.cpp | 25 +- core/predictor/framework/resource.h | 1 + core/predictor/proto/CMakeLists.txt | 1 + core/predictor/proto/framework.proto | 217 ++++++++++++++++++ doc/criteo-cube-benchmark-avgcost.png | Bin 0 -> 29877 bytes doc/criteo-cube-benchmark-qps.png | Bin 0 -> 21593 bytes .../examples/criteo_ctr_with_cube/README.md | 62 +++++ python/examples/criteo_ctr_with_cube/args.py | 105 +++++++++ .../criteo_ctr_with_cube/benchmark.py | 81 +++++++ .../criteo_ctr_with_cube/benchmark.sh | 10 + .../criteo_ctr_with_cube/benchmark_batch.py | 85 +++++++ .../criteo_ctr_with_cube/benchmark_batch.sh | 12 + python/examples/criteo_ctr_with_cube/clean.sh | 5 + .../examples/criteo_ctr_with_cube/criteo.py | 81 +++++++ .../criteo_ctr_with_cube/criteo_reader.py | 83 +++++++ .../criteo_ctr_with_cube/cube/conf/cube.conf | 13 ++ .../cube/conf/gflags.conf | 4 + .../cube/conf/transfer.conf | 17 ++ .../examples/criteo_ctr_with_cube/cube/keys | 10 + .../criteo_ctr_with_cube/cube_prepare.sh | 7 + .../examples/criteo_ctr_with_cube/get_data.sh | 2 + .../criteo_ctr_with_cube/local_train.py | 100 ++++++++ .../criteo_ctr_with_cube/network_conf.py | 77 +++++++ .../criteo_ctr_with_cube/test_client.py | 52 +++++ .../criteo_ctr_with_cube/test_server.py | 36 +++ python/paddle_serving_server/__init__.py | 9 +- 34 files changed, 1553 insertions(+), 9 deletions(-) create mode 100644 core/general-server/op/general_dist_kv_infer_op.cpp create mode 100644 core/general-server/op/general_dist_kv_infer_op.h create mode 100644 core/predictor/common/seq_file.cpp create mode 100644 core/predictor/common/seq_file.h create mode 100644 core/predictor/common/seq_generator.cpp create mode 100644 core/predictor/proto/framework.proto create mode 100644 doc/criteo-cube-benchmark-avgcost.png create mode 100644 doc/criteo-cube-benchmark-qps.png create mode 100644 python/examples/criteo_ctr_with_cube/README.md create mode 100644 python/examples/criteo_ctr_with_cube/args.py create mode 100644 python/examples/criteo_ctr_with_cube/benchmark.py create mode 100644 python/examples/criteo_ctr_with_cube/benchmark.sh create mode 100644 python/examples/criteo_ctr_with_cube/benchmark_batch.py create mode 100644 python/examples/criteo_ctr_with_cube/benchmark_batch.sh create mode 100644 python/examples/criteo_ctr_with_cube/clean.sh create mode 100644 python/examples/criteo_ctr_with_cube/criteo.py create mode 100644 python/examples/criteo_ctr_with_cube/criteo_reader.py create mode 100644 python/examples/criteo_ctr_with_cube/cube/conf/cube.conf create mode 100644 python/examples/criteo_ctr_with_cube/cube/conf/gflags.conf create mode 100644 python/examples/criteo_ctr_with_cube/cube/conf/transfer.conf create mode 100644 python/examples/criteo_ctr_with_cube/cube/keys create mode 100644 python/examples/criteo_ctr_with_cube/cube_prepare.sh create mode 100644 python/examples/criteo_ctr_with_cube/get_data.sh create mode 100644 python/examples/criteo_ctr_with_cube/local_train.py create mode 100644 python/examples/criteo_ctr_with_cube/network_conf.py create mode 100644 python/examples/criteo_ctr_with_cube/test_client.py create mode 100644 python/examples/criteo_ctr_with_cube/test_server.py diff --git a/core/configure/proto/server_configure.proto b/core/configure/proto/server_configure.proto index cc508e8e..bbc4487d 100644 --- a/core/configure/proto/server_configure.proto +++ b/core/configure/proto/server_configure.proto @@ -52,9 +52,10 @@ message ModelToolkitConf { repeated EngineDesc engines = 1; }; message ResourceConf { required string model_toolkit_path = 1; required string model_toolkit_file = 2; - optional string cube_config_file = 3; - optional string general_model_path = 4; - optional string general_model_file = 5; + optional string general_model_path = 3; + optional string general_model_file = 4; + optional string cube_config_path = 5; + optional string cube_config_file = 6; }; // DAG node depency info diff --git a/core/general-server/CMakeLists.txt b/core/general-server/CMakeLists.txt index 87d1c284..7cf0a04d 100644 --- a/core/general-server/CMakeLists.txt +++ b/core/general-server/CMakeLists.txt @@ -1,4 +1,5 @@ include_directories(SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../kvdb/include) +include_directories(SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../../) include(op/CMakeLists.txt) include(proto/CMakeLists.txt) add_executable(serving ${serving_srcs}) diff --git a/core/general-server/op/general_dist_kv_infer_op.cpp b/core/general-server/op/general_dist_kv_infer_op.cpp new file mode 100644 index 00000000..8011e96c --- /dev/null +++ b/core/general-server/op/general_dist_kv_infer_op.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// 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. + +#include "core/general-server/op/general_dist_kv_infer_op.h" +#include +#include +#include +#include +#include "core/cube/cube-api/include/cube_api.h" +#include "core/predictor/framework/infer.h" +#include "core/predictor/framework/memory.h" +#include "core/predictor/framework/resource.h" +#include "core/util/include/timer.h" + +namespace baidu { +namespace paddle_serving { +namespace serving { + +using baidu::paddle_serving::Timer; +using baidu::paddle_serving::predictor::MempoolWrapper; +using baidu::paddle_serving::predictor::general_model::Tensor; +using baidu::paddle_serving::predictor::general_model::Response; +using baidu::paddle_serving::predictor::general_model::Request; +using baidu::paddle_serving::predictor::general_model::FetchInst; +using baidu::paddle_serving::predictor::InferManager; +using baidu::paddle_serving::predictor::PaddleGeneralModelConfig; + +int GeneralDistKVInferOp::inference() { + VLOG(2) << "Going to run inference"; + const GeneralBlob *input_blob = get_depend_argument(pre_name()); + VLOG(2) << "Get precedent op name: " << pre_name(); + GeneralBlob *output_blob = mutable_data(); + + if (!input_blob) { + LOG(ERROR) << "Failed mutable depended argument, op:" << pre_name(); + return -1; + } + + const TensorVector *in = &input_blob->tensor_vector; + TensorVector *out = &output_blob->tensor_vector; + int batch_size = input_blob->GetBatchSize(); + VLOG(2) << "input batch size: " << batch_size; + std::vector keys; + std::vector values; + int sparse_count = 0; + int dense_count = 0; + for (size_t i = 0; i < in->size(); ++i) { + if (in->at(i).dtype != paddle::PaddleDType::INT64) { + ++dense_count; + continue; + } + ++sparse_count; + size_t elem_num = 1; + for (size_t s = 0; s < in->at(i).shape.size(); ++s) { + elem_num *= in->at(i).shape[s]; + } + int64_t *data_ptr = static_cast(in->at(i).data.data()); + for (size_t j = 0; j < elem_num; ++j) { + keys.push_back(data_ptr[j]); + } + } + // TODO: Add Seek CubeValues Here, and replace EMBEDDING_SIZE with variable. + rec::mcube::CubeAPI *cube = rec::mcube::CubeAPI::instance(); + // TODO: temp hard code "test_dict" here, fix this with next commit + // related to cube conf + std::string table_name = "test_dict"; + int ret = cube->seek(table_name, keys, &values); + + if (values.size() != keys.size() || values[0].buff.size() == 0) { + LOG(ERROR) << "cube value return null"; + } + size_t EMBEDDING_SIZE = values[0].buff.size() / 4; + TensorVector sparse_out; + sparse_out.resize(sparse_count); + TensorVector dense_out; + dense_out.resize(dense_count); + int cube_val_idx = 0; + int sparse_idx = 0; + int dense_idx = 0; + std::unordered_map in_out_map; + baidu::paddle_serving::predictor::Resource &resource = + baidu::paddle_serving::predictor::Resource::instance(); + std::shared_ptr model_config = + resource.get_general_model_config(); + for (size_t i = 0; i < in->size(); ++i) { + if (in->at(i).dtype != paddle::PaddleDType::INT64) { + dense_out[dense_idx] = in->at(i); + ++dense_idx; + continue; + } + + sparse_out[sparse_idx].lod.resize(in->at(i).lod.size()); + for (size_t x = 0; x < sparse_out[sparse_idx].lod.size(); ++x) { + sparse_out[sparse_idx].lod[x].resize(in->at(i).lod[x].size()); + std::copy(in->at(i).lod[x].begin(), + in->at(i).lod[x].end(), + sparse_out[sparse_idx].lod[x].begin()); + } + sparse_out[sparse_idx].dtype = paddle::PaddleDType::FLOAT32; + sparse_out[sparse_idx].shape.push_back( + sparse_out[sparse_idx].lod[0].back()); + sparse_out[sparse_idx].shape.push_back(EMBEDDING_SIZE); + sparse_out[sparse_idx].name = model_config->_feed_alias_name[i]; + sparse_out[sparse_idx].data.Resize(sparse_out[sparse_idx].lod[0].back() * + EMBEDDING_SIZE * sizeof(float)); + float *dst_ptr = static_cast(sparse_out[sparse_idx].data.data()); + for (int x = 0; x < sparse_out[sparse_idx].lod[0].back(); ++x) { + float *data_ptr = dst_ptr + x * EMBEDDING_SIZE; + memcpy(data_ptr, + values[cube_val_idx].buff.data(), + values[cube_val_idx].buff.size()); + cube_val_idx++; + } + ++sparse_idx; + } + TensorVector infer_in; + infer_in.insert(infer_in.end(), dense_out.begin(), dense_out.end()); + infer_in.insert(infer_in.end(), sparse_out.begin(), sparse_out.end()); + + output_blob->SetBatchSize(batch_size); + + VLOG(2) << "infer batch size: " << batch_size; + + Timer timeline; + int64_t start = timeline.TimeStampUS(); + timeline.Start(); + + if (InferManager::instance().infer( + GENERAL_MODEL_NAME, &infer_in, out, batch_size)) { + LOG(ERROR) << "Failed do infer in fluid model: " << GENERAL_MODEL_NAME; + return -1; + } + + int64_t end = timeline.TimeStampUS(); + CopyBlobInfo(input_blob, output_blob); + AddBlobInfo(output_blob, start); + AddBlobInfo(output_blob, end); + return 0; +} +DEFINE_OP(GeneralDistKVInferOp); + +} // namespace serving +} // namespace paddle_serving +} // namespace baidu diff --git a/core/general-server/op/general_dist_kv_infer_op.h b/core/general-server/op/general_dist_kv_infer_op.h new file mode 100644 index 00000000..e6085f7a --- /dev/null +++ b/core/general-server/op/general_dist_kv_infer_op.h @@ -0,0 +1,46 @@ +// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +// +// 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. + +#pragma once +#include +#include +#ifdef BCLOUD +#ifdef WITH_GPU +#include "paddle/paddle_inference_api.h" +#else +#include "paddle/fluid/inference/api/paddle_inference_api.h" +#endif +#else +#include "paddle_inference_api.h" // NOLINT +#endif +#include "core/general-server/general_model_service.pb.h" +#include "core/general-server/op/general_infer_helper.h" + +namespace baidu { +namespace paddle_serving { +namespace serving { + +class GeneralDistKVInferOp + : public baidu::paddle_serving::predictor::OpWithChannel { + public: + typedef std::vector TensorVector; + + DECLARE_OP(GeneralDistKVInferOp); + + int inference(); +}; + +} // namespace serving +} // namespace paddle_serving +} // namespace baidu diff --git a/core/predictor/common/CMakeLists.txt b/core/predictor/common/CMakeLists.txt index 0eebb3a7..a6078feb 100644 --- a/core/predictor/common/CMakeLists.txt +++ b/core/predictor/common/CMakeLists.txt @@ -1,2 +1,7 @@ -FILE(GLOB common_srcs ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +FILE(GLOB common_srcs ${CMAKE_CURRENT_LIST_DIR}/constant.cpp) LIST(APPEND pdserving_srcs ${common_srcs}) + +set(seq_gen_src ${CMAKE_CURRENT_LIST_DIR}/seq_generator.cpp ${CMAKE_CURRENT_LIST_DIR}/seq_file.cpp) +LIST(APPEND seq_gen_src ${PROTO_SRCS}) +add_executable(seq_generator ${seq_gen_src}) +target_link_libraries(seq_generator protobuf -lpthread) diff --git a/core/predictor/common/seq_file.cpp b/core/predictor/common/seq_file.cpp new file mode 100644 index 00000000..3c85d918 --- /dev/null +++ b/core/predictor/common/seq_file.cpp @@ -0,0 +1,83 @@ +// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +// +// 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. + +#include +#include +#include +#include +#include + +#include +#include "seq_file.h" + +SeqFileWriter::SeqFileWriter(const char *file) { + _fs = new std::ofstream(file, std::ios::binary); + std::srand(std::time(0)); + for (int i = 0; i < SYNC_MARKER_SIZE; ++i) { + _sync_marker[i] = std::rand() % 255; + } + + _write_seq_header(); + + _bytes_to_prev_sync = 0; +} + +void SeqFileWriter::close() { + _fs->close(); + delete _fs; +} + +SeqFileWriter::~SeqFileWriter() { close(); } + +void SeqFileWriter::_write_sync_marker() { + char begin[] = {'\xFF', '\xFF', '\xFF', '\xFF'}; + _fs->write(begin, 4); + + _fs->write(_sync_marker, SYNC_MARKER_SIZE); +} + +void SeqFileWriter::_write_seq_header() { + _fs->write(SEQ_HEADER, sizeof(SEQ_HEADER) - 1); + _fs->write(_sync_marker, SYNC_MARKER_SIZE); +} + +int SeqFileWriter::write(const char *key, + size_t key_len, + const char *value, + size_t value_len) { + if (key_len != sizeof(int64_t)) { + std::cout << "Key length not equal to " << sizeof(int64_t) << std::endl; + return -1; + } + + uint32_t record_len = key_len + value_len; + uint32_t b_record_len = htonl(record_len); + uint32_t b_key_len = htonl((uint32_t)key_len); + // std::cout << "b_record_len " << b_record_len << " record_len " << + // record_len << std::endl; + _fs->write((char *)&b_record_len, sizeof(uint32_t)); + _fs->write((char *)&b_key_len, sizeof(uint32_t)); + _fs->write(key, key_len); + _fs->write(value, value_len); + _bytes_to_prev_sync += record_len; + + if (_bytes_to_prev_sync >= SYNC_INTERVAL) { + _write_sync_marker(); + _bytes_to_prev_sync = 0; + } + + return 0; +} + +/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */ diff --git a/core/predictor/common/seq_file.h b/core/predictor/common/seq_file.h new file mode 100644 index 00000000..95aa5403 --- /dev/null +++ b/core/predictor/common/seq_file.h @@ -0,0 +1,52 @@ +// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +// +// 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. + +#ifndef __SEQ_FILE_H_ +#define __SEQ_FILE_H_ + +#include + +const int SYNC_MARKER_SIZE = 16; +const char SEQ_HEADER[] = + "SEQ\x06" + "\"org.apache.hadoop.io.BytesWritable\"" + "org.apache.hadoop.io.BytesWritable" + "\x00\x00\x00\x00\x00\x00"; +const int SYNC_INTERVAL = 2000; + +class SeqFileWriter { + public: + SeqFileWriter(const char *file); + ~SeqFileWriter(); + + public: + int write(const char *key, + size_t key_len, + const char *value, + size_t value_len); + + private: + void close(); + void _write_sync_marker(); + void _write_seq_header(); + + private: + char _sync_marker[SYNC_MARKER_SIZE]; + int _bytes_to_prev_sync; + std::ofstream *_fs; +}; + +#endif //__SEQ_FILE_H_ + +/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */ diff --git a/core/predictor/common/seq_generator.cpp b/core/predictor/common/seq_generator.cpp new file mode 100644 index 00000000..f6d2ac30 --- /dev/null +++ b/core/predictor/common/seq_generator.cpp @@ -0,0 +1,116 @@ +// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +// +// 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. + +#include +#include +#include +#include "core/predictor/framework.pb.h" +#include "seq_file.h" +using paddle::framework::proto::VarType; +std::map var_type_size; +void reg_var_types() { + var_type_size[static_cast(VarType::FP16)] = sizeof(int16_t); + var_type_size[static_cast(VarType::FP32)] = sizeof(float); + var_type_size[static_cast(VarType::FP64)] = sizeof(double); + var_type_size[static_cast(VarType::INT32)] = sizeof(int); + var_type_size[static_cast(VarType::INT64)] = sizeof(int64_t); + var_type_size[static_cast(VarType::BOOL)] = sizeof(bool); + var_type_size[static_cast(VarType::SIZE_T)] = sizeof(size_t); + var_type_size[static_cast(VarType::INT16)] = sizeof(int16_t); + var_type_size[static_cast(VarType::UINT8)] = sizeof(uint8_t); + var_type_size[static_cast(VarType::INT8)] = sizeof(int8_t); +} +int dump_parameter(const char *input_file, const char *output_file) { + std::ifstream is(input_file); + // the 1st field, unit32_t version for LoDTensor + uint32_t version; + is.read(reinterpret_cast(&version), sizeof(version)); + if (version != 0) { + std::cout << "Version number " << version << " not supported" << std::endl; + return -1; + } + // the 2st field, LoD information + uint64_t lod_level; + is.read(reinterpret_cast(&lod_level), sizeof(lod_level)); + std::vector> lod; + lod.resize(lod_level); + for (uint64_t i = 0; i < lod_level; ++i) { + uint64_t size; + is.read(reinterpret_cast(&size), sizeof(size)); + std::vector tmp(size / sizeof(size_t)); + is.read(reinterpret_cast(tmp.data()), + static_cast(size)); + lod[i] = tmp; + } + // the 3st filed, Tensor + // Note: duplicate version field + is.read(reinterpret_cast(&version), sizeof(version)); + if (version != 0) { + std::cout << "Version number " << version << " not supported" << std::endl; + return -1; + } + // int32_t size + // proto buffer + VarType::TensorDesc desc; + int32_t size; + is.read(reinterpret_cast(&size), sizeof(size)); + std::unique_ptr buf(new char[size]); + is.read(reinterpret_cast(buf.get()), size); + if (!desc.ParseFromArray(buf.get(), size)) { + std::cout << "Cannot parse tensor desc" << std::endl; + return -1; + } + // read tensor + std::vector dims; + dims.reserve(static_cast(desc.dims().size())); + std::copy(desc.dims().begin(), desc.dims().end(), std::back_inserter(dims)); + std::cout << "Dims:"; + for (auto x : dims) { + std::cout << " " << x; + } + std::cout << std::endl; + if (dims.size() != 2) { + std::cout << "Parameter dims not 2D" << std::endl; + return -1; + } + size_t numel = 1; + for (auto x : dims) { + numel *= x; + } + size_t buf_size = numel * var_type_size[desc.data_type()]; + char *tensor_buf = new char[buf_size]; + is.read(static_cast(tensor_buf), buf_size); + is.close(); + SeqFileWriter seq_file_writer(output_file); + int value_buf_len = var_type_size[desc.data_type()] * dims[1]; + char *value_buf = new char[value_buf_len]; + size_t offset = 0; + for (int64_t i = 0; i < dims[0]; ++i) { + // std::cout << "key_len " << key_len << " value_len " << value_buf_len << + // std::endl; + memcpy(value_buf, tensor_buf + offset, value_buf_len); + seq_file_writer.write((char *)&i, sizeof(i), value_buf, value_buf_len); + offset += value_buf_len; + } + return 0; +} +int main(int argc, char **argv) { + if (argc != 3) { + std::cout << "Usage: seq_generator PARAMETER_FILE OUTPUT_FILE" << std::endl; + return -1; + } + reg_var_types(); + dump_parameter(argv[1], argv[2]); +} +/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */ diff --git a/core/predictor/framework/resource.cpp b/core/predictor/framework/resource.cpp index 943b1715..07010ac8 100644 --- a/core/predictor/framework/resource.cpp +++ b/core/predictor/framework/resource.cpp @@ -143,7 +143,15 @@ int Resource::initialize(const std::string& path, const std::string& file) { LOG(ERROR) << "unable to create tls_bthread_key of thrd_data"; return -1; } - // init rocksDB instance + // init rocksDB or cube instance + if (resource_conf.has_cube_config_file() && resource_conf.has_cube_config_path()) { + LOG(INFO) << "init cube client, path[ " << resource_conf.cube_config_path() + << " ], config file [ " << resource_conf.cube_config_file() << " ]."; + rec::mcube::CubeAPI *cube = rec::mcube::CubeAPI::instance(); + std::string cube_config_fullpath = "./" + resource_conf.cube_config_path() +"/" + resource_conf.cube_config_file(); + this->cube_config_fullpath = cube_config_fullpath; + } + if (db.get() == nullptr) { db = RocksDBWrapper::RocksDBWrapperFactory("kvdb"); } @@ -155,6 +163,12 @@ int Resource::initialize(const std::string& path, const std::string& file) { // model config int Resource::general_model_initialize(const std::string& path, const std::string& file) { + // TODO: add serving dist op detection, if true, add cube instance init. + if (this->cube_config_fullpath.size() != 0) { + LOG(INFO) << "init cube by config file : " << this->cube_config_fullpath; + rec::mcube::CubeAPI *cube = rec::mcube::CubeAPI::instance(); + cube->init(this->cube_config_fullpath.c_str()); + } VLOG(2) << "general model path: " << path; VLOG(2) << "general model file: " << file; if (!FLAGS_enable_general_model) { @@ -197,10 +211,13 @@ int Resource::general_model_initialize(const std::string& path, for (int i = 0; i < feed_var_num; ++i) { _config->_feed_name[i] = model_config.feed_var(i).name(); _config->_feed_alias_name[i] = model_config.feed_var(i).alias_name(); - VLOG(2) << "feed var[" << i << "]: " << _config->_feed_name[i]; - VLOG(2) << "feed var[" << i << "]: " << _config->_feed_alias_name[i]; + VLOG(2) << "feed var[" << i << "]: " + << _config->_feed_name[i]; + VLOG(2) << "feed var[" << i << "]: " + << _config->_feed_alias_name[i]; _config->_feed_type[i] = model_config.feed_var(i).feed_type(); - VLOG(2) << "feed type[" << i << "]: " << _config->_feed_type[i]; + VLOG(2) << "feed type[" << i << "]: " + << _config->_feed_type[i]; if (model_config.feed_var(i).is_lod_tensor()) { VLOG(2) << "var[" << i << "] is lod tensor"; diff --git a/core/predictor/framework/resource.h b/core/predictor/framework/resource.h index eedd4fc1..70461f48 100644 --- a/core/predictor/framework/resource.h +++ b/core/predictor/framework/resource.h @@ -112,6 +112,7 @@ class Resource { int thread_finalize() { return 0; } std::shared_ptr db; std::shared_ptr _config; + std::string cube_config_fullpath; THREAD_KEY_T _tls_bspec_key; }; diff --git a/core/predictor/proto/CMakeLists.txt b/core/predictor/proto/CMakeLists.txt index 773b5bfc..1a9e2d35 100644 --- a/core/predictor/proto/CMakeLists.txt +++ b/core/predictor/proto/CMakeLists.txt @@ -7,6 +7,7 @@ LIST(APPEND protofiles ${CMAKE_CURRENT_LIST_DIR}/./builtin_format.proto ${CMAKE_CURRENT_LIST_DIR}/./msg_data.proto ${CMAKE_CURRENT_LIST_DIR}/./xrecord_format.proto + ${CMAKE_CURRENT_LIST_DIR}/./framework.proto ) PROTOBUF_GENERATE_SERVING_CPP(TRUE PROTO_SRCS PROTO_HDRS ${protofiles}) diff --git a/core/predictor/proto/framework.proto b/core/predictor/proto/framework.proto new file mode 100644 index 00000000..5adf2a18 --- /dev/null +++ b/core/predictor/proto/framework.proto @@ -0,0 +1,217 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. + +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. */ + +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; +package paddle.framework.proto; + +// Any incompatible changes to ProgramDesc and its dependencies should +// raise the version defined version.h. +// +// Serailization and Deserialization codes should be modified in a way +// that supports old versions following the version and compatibility policy. +message Version { optional int64 version = 1 [ default = 0 ]; } + +enum AttrType { + INT = 0; + FLOAT = 1; + STRING = 2; + INTS = 3; + FLOATS = 4; + STRINGS = 5; + BOOLEAN = 6; + BOOLEANS = 7; + BLOCK = 8; + LONG = 9; + BLOCKS = 10; + LONGS = 11; +} + +// OpDesc describes an instance of a C++ framework::OperatorBase +// derived class type. +message OpDesc { + + message Attr { + required string name = 1; + required AttrType type = 2; + optional int32 i = 3; + optional float f = 4; + optional string s = 5; + repeated int32 ints = 6; + repeated float floats = 7; + repeated string strings = 8; + optional bool b = 10; + repeated bool bools = 11; + optional int32 block_idx = 12; + optional int64 l = 13; + repeated int32 blocks_idx = 14; + repeated int64 longs = 15; + }; + + message Var { + required string parameter = 1; + repeated string arguments = 2; + }; + + required string type = 3; + repeated Var inputs = 1; + repeated Var outputs = 2; + repeated Attr attrs = 4; + optional bool is_target = 5 [ default = false ]; +}; + +// OpProto describes a C++ framework::OperatorBase derived class. +message OpProto { + + // VarProto describes the C++ type framework::Variable. + message Var { + required string name = 1; + required string comment = 2; + + optional bool duplicable = 3 [ default = false ]; + optional bool intermediate = 4 [ default = false ]; + optional bool dispensable = 5 [ default = false ]; + } + + // AttrProto describes the C++ type Attribute. + message Attr { + required string name = 1; + required AttrType type = 2; + required string comment = 3; + // If that attribute is generated, it means the Paddle third + // language binding has responsibility to fill that + // attribute. End-User should not set that attribute. + optional bool generated = 4 [ default = false ]; + } + + required string type = 1; + repeated Var inputs = 2; + repeated Var outputs = 3; + repeated Attr attrs = 4; + required string comment = 5; +} + +message VarType { + enum Type { + // Pod Types + BOOL = 0; + INT16 = 1; + INT32 = 2; + INT64 = 3; + FP16 = 4; + FP32 = 5; + FP64 = 6; + // Tensor is used in C++. + SIZE_T = 19; + UINT8 = 20; + INT8 = 21; + + // Other types that may need additional descriptions + LOD_TENSOR = 7; + SELECTED_ROWS = 8; + FEED_MINIBATCH = 9; + FETCH_LIST = 10; + STEP_SCOPES = 11; + LOD_RANK_TABLE = 12; + LOD_TENSOR_ARRAY = 13; + PLACE_LIST = 14; + READER = 15; + // Any runtime decided variable type is raw + // raw variables should manage their own allocations + // in operators like nccl_op + RAW = 17; + TUPLE = 18; + } + + required Type type = 1; + + message TensorDesc { + // Should only be PODType. Is enforced in C++ + required Type data_type = 1; + repeated int64 dims = 2; // [UNK, 640, 480] is saved as [-1, 640, 480] + } + optional TensorDesc selected_rows = 2; + + message LoDTensorDesc { + required TensorDesc tensor = 1; + optional int32 lod_level = 2 [ default = 0 ]; + } + optional LoDTensorDesc lod_tensor = 3; + + message LoDTensorArrayDesc { + required TensorDesc tensor = 1; + optional int32 lod_level = 2 [ default = 0 ]; + } + optional LoDTensorArrayDesc tensor_array = 4; + + message ReaderDesc { repeated LoDTensorDesc lod_tensor = 1; } + optional ReaderDesc reader = 5; + + message Tuple { repeated Type element_type = 1; } + optional Tuple tuple = 7; +} + +message VarDesc { + required string name = 1; + required VarType type = 2; + optional bool persistable = 3 [ default = false ]; + // True if the variable is an input data and + // have to check the feed data shape and dtype + optional bool need_check_feed = 4 [ default = false ]; +} + +message BlockDesc { + required int32 idx = 1; + required int32 parent_idx = 2; + repeated VarDesc vars = 3; + repeated OpDesc ops = 4; + optional int32 forward_block_idx = 5 [ default = -1 ]; +} + +// CompatibleInfo is used to determine if a feature is compatible and +// provides the information. +message CompatibleInfo { + enum Type { + COMPATIBLE = 0; + DEFINITELY_NOT = 1; + POSSIBLE = 2; + BUG_FIX = 3; + PRECISION_CHANGE = 4; + } + required string version = 1; + required Type type = 2; +} + +// In some cases, Paddle Fluid may perform operator definition iterations, +// and the operator uses OpCompatibleMap for compatibility testing. +message OpCompatibleMap { + message OpCompatiblePair { + required string op_name = 1; + required CompatibleInfo compatible_info = 2; + } + repeated OpCompatiblePair pair = 1; + optional string default_required_version = 2; +} + +// Please refer to +// https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/program.md +// for more details. +// TODO(panyx0718): A model can have multiple programs. Need a +// way to distinguish them. Maybe ID or name? +message ProgramDesc { + reserved 2; // For backward compatibility. + repeated BlockDesc blocks = 1; + optional Version version = 4; + optional OpCompatibleMap op_compatible_map = 3; +} diff --git a/doc/criteo-cube-benchmark-avgcost.png b/doc/criteo-cube-benchmark-avgcost.png new file mode 100644 index 0000000000000000000000000000000000000000..1dbf27756b61f62d10f2527b11760051a185d818 GIT binary patch literal 29877 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 O54@V+D*JupwH=l;fXkZJ6^8 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 literal 0 HcmV?d00001 diff --git a/doc/criteo-cube-benchmark-qps.png b/doc/criteo-cube-benchmark-qps.png new file mode 100644 index 0000000000000000000000000000000000000000..2abac020af6ccaad7b90b1433621f0740be1f06e GIT binary patch literal 21593 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 WH^w8Gs^FEOD6+Q|Z{^)I@cUmgJsm&* literal 0 HcmV?d00001 diff --git a/python/examples/criteo_ctr_with_cube/README.md b/python/examples/criteo_ctr_with_cube/README.md new file mode 100644 index 00000000..83c70583 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/README.md @@ -0,0 +1,62 @@ +## 带稀疏参数服务器的CTR预测服务 + +### 获取样例数据 +``` +sh get_data.sh +``` + +### 保存模型和配置文件 +``` +python local_train.py +``` +执行脚本后会在当前目录生成ctr_server_model和ctr_client_config文件夹,以及ctr_server_model_kv, ctr_client_conf_kv。 + +### 启动稀疏参数服务器 +``` +cp ../../../build_server/core/predictor/seq_generator seq_generator +cp ../../../build_server/output/bin/cube* ./cube/ +cp ../../../build_server/core/cube/cube-api/cube-cli ./cube/ +sh cube_prepare.sh & +``` + +### 启动RPC预测服务 + +``` +python test_server.py ctr_serving_model_kv +``` + +### 执行预测 + +``` +python test_client.py ctr_client_conf/serving_client_conf.prototxt ./raw_data +``` + +### Benchmark + +设备 :Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHz + +模型 :[Criteo CTR](https://github.com/PaddlePaddle/Serving/blob/develop/python/examples/ctr_criteo_with_cube/network_conf.py) + +server thread num : 16 + +执行 +``` +bash benchmark.sh +``` +客户端每个线程会发送1000个batch + +| client thread num | prepro | client infer | op0 | op1 | op2 | postpro | avg_cost | qps | +| ------------------ | ------ | ------------ | ------ | ----- | ------ | ------- | ----- | ----- | +| 1 | 0.027 | 1.582 | 0.019 | 0.856 | 0.0024 | 0.0019 | 5.984 | 5.990 | +| 2 | 0.028 | 1.674 | 0.021 | 0.870 | 0.0020 | 0.0021 | 6.305 | 6.432 | +| 4 | 0.031 | 1.810 | 0.021 | 0.878 | 0.0039 | 0.0023 | 6.597 | 6.585 | +| 8 | 0.043 | 3.185 | 0.023 | 0.929 | 0.0026 | 0.0029 | 9.312 | 9.263 | +| 16 | 0.055 | 11.641 | 0.028 | 1.244 | 0.0042 | 0.0039 | 18.993 | 18.540 | + +平均每个线程耗时图如下 + +![avg cost](../../../doc/criteo-cube-benchmark-avgcost.png) + +每个线程QPS耗时如下 + +![qps](../../../doc/criteo-cube-benchmark-qps.png) diff --git a/python/examples/criteo_ctr_with_cube/args.py b/python/examples/criteo_ctr_with_cube/args.py new file mode 100644 index 00000000..30124d4e --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/args.py @@ -0,0 +1,105 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing +import argparse + + +def parse_args(): + parser = argparse.ArgumentParser(description="PaddlePaddle CTR example") + parser.add_argument( + '--train_data_path', + type=str, + default='./data/raw/train.txt', + help="The path of training dataset") + parser.add_argument( + '--sparse_only', + type=bool, + default=False, + help="Whether we use sparse features only") + parser.add_argument( + '--test_data_path', + type=str, + default='./data/raw/valid.txt', + help="The path of testing dataset") + parser.add_argument( + '--batch_size', + type=int, + default=1000, + help="The size of mini-batch (default:1000)") + parser.add_argument( + '--embedding_size', + type=int, + default=10, + help="The size for embedding layer (default:10)") + parser.add_argument( + '--num_passes', + type=int, + default=10, + help="The number of passes to train (default: 10)") + parser.add_argument( + '--model_output_dir', + type=str, + default='models', + help='The path for model to store (default: models)') + parser.add_argument( + '--sparse_feature_dim', + type=int, + default=1000001, + help='sparse feature hashing space for index processing') + parser.add_argument( + '--is_local', + type=int, + default=1, + help='Local train or distributed train (default: 1)') + parser.add_argument( + '--cloud_train', + type=int, + default=0, + help='Local train or distributed train on paddlecloud (default: 0)') + parser.add_argument( + '--async_mode', + action='store_true', + default=False, + help='Whether start pserver in async mode to support ASGD') + parser.add_argument( + '--no_split_var', + action='store_true', + default=False, + help='Whether split variables into blocks when update_method is pserver') + parser.add_argument( + '--role', + type=str, + default='pserver', # trainer or pserver + help='The path for model to store (default: models)') + parser.add_argument( + '--endpoints', + type=str, + default='127.0.0.1:6000', + help='The pserver endpoints, like: 127.0.0.1:6000,127.0.0.1:6001') + parser.add_argument( + '--current_endpoint', + type=str, + default='127.0.0.1:6000', + help='The path for model to store (default: 127.0.0.1:6000)') + parser.add_argument( + '--trainer_id', + type=int, + default=0, + help='The path for model to store (default: models)') + parser.add_argument( + '--trainers', + type=int, + default=1, + help='The num of trianers, (default: 1)') + return parser.parse_args() diff --git a/python/examples/criteo_ctr_with_cube/benchmark.py b/python/examples/criteo_ctr_with_cube/benchmark.py new file mode 100644 index 00000000..3e33f662 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/benchmark.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +from paddle_serving_client import Client +import sys +import os +import criteo as criteo +import time +from paddle_serving_client.utils import MultiThreadRunner +from paddle_serving_client.utils import benchmark_args +from paddle_serving_client.metric import auc + +args = benchmark_args() + + +def single_func(idx, resource): + client = Client() + print([resource["endpoint"][idx % len(resource["endpoint"])]]) + client.load_client_config('ctr_client_conf/serving_client_conf.prototxt') + client.connect(['127.0.0.1:9292']) + batch = 1 + buf_size = 100 + dataset = criteo.CriteoDataset() + dataset.setup(1000001) + test_filelists = [ + "./raw_data/part-%d" % x for x in range(len(os.listdir("./raw_data"))) + ] + reader = dataset.infer_reader(test_filelists[len(test_filelists) - 40:], + batch, buf_size) + args.batch_size = 1 + if args.request == "rpc": + fetch = ["prob"] + print("Start Time") + start = time.time() + itr = 1000 + for ei in range(itr): + if args.batch_size == 1: + data = reader().next() + feed_dict = {} + feed_dict['dense_input'] = data[0][0] + for i in range(1, 27): + feed_dict["embedding_{}.tmp_0".format(i - 1)] = data[0][i] + result = client.predict(feed=feed_dict, fetch=fetch) + else: + print("unsupport batch size {}".format(args.batch_size)) + + elif args.request == "http": + raise ("Not support http service.") + end = time.time() + qps = itr / (end - start) + return [[end - start, qps]] + + +if __name__ == '__main__': + multi_thread_runner = MultiThreadRunner() + endpoint_list = ["127.0.0.1:9292"] + #result = single_func(0, {"endpoint": endpoint_list}) + result = multi_thread_runner.run(single_func, args.thread, + {"endpoint": endpoint_list}) + avg_cost = 0 + qps = 0 + for i in range(args.thread): + avg_cost += result[0][i * 2 + 0] + qps += result[0][i * 2 + 1] + avg_cost = avg_cost / args.thread + print("average total cost {} s.".format(avg_cost)) + print("qps {} ins/s".format(qps)) diff --git a/python/examples/criteo_ctr_with_cube/benchmark.sh b/python/examples/criteo_ctr_with_cube/benchmark.sh new file mode 100644 index 00000000..4bea258a --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/benchmark.sh @@ -0,0 +1,10 @@ +rm profile_log +batch_size=1 +for thread_num in 1 2 4 8 16 +do + $PYTHONROOT/bin/python benchmark.py --thread $thread_num --model ctr_client_conf/serving_client_conf.prototxt --request rpc > profile 2>&1 + echo "========================================" + echo "batch size : $batch_size" >> profile_log + $PYTHONROOT/bin/python ../util/show_profile.py profile $thread_num >> profile_log + tail -n 2 profile >> profile_log +done diff --git a/python/examples/criteo_ctr_with_cube/benchmark_batch.py b/python/examples/criteo_ctr_with_cube/benchmark_batch.py new file mode 100644 index 00000000..b4b15892 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/benchmark_batch.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +from paddle_serving_client import Client +import sys +import os +import criteo as criteo +import time +from paddle_serving_client.utils import MultiThreadRunner +from paddle_serving_client.utils import benchmark_args +from paddle_serving_client.metric import auc + +args = benchmark_args() + + +def single_func(idx, resource): + client = Client() + print([resource["endpoint"][idx % len(resource["endpoint"])]]) + client.load_client_config('ctr_client_conf/serving_client_conf.prototxt') + client.connect(['127.0.0.1:9292']) + batch = 1 + buf_size = 100 + dataset = criteo.CriteoDataset() + dataset.setup(1000001) + test_filelists = [ + "./raw_data/part-%d" % x for x in range(len(os.listdir("./raw_data"))) + ] + reader = dataset.infer_reader(test_filelists[len(test_filelists) - 40:], + batch, buf_size) + if args.request == "rpc": + fetch = ["prob"] + start = time.time() + itr = 1000 + for ei in range(itr): + if args.batch_size > 1: + feed_batch = [] + for bi in range(args.batch_size): + data = reader().next() + feed_dict = {} + feed_dict['dense_input'] = data[0][0] + for i in range(1, 27): + feed_dict["embedding_{}.tmp_0".format(i - 1)] = data[0][ + i] + feed_batch.append(feed_dict) + result = client.batch_predict( + feed_batch=feed_batch, fetch=fetch) + else: + print("unsupport batch size {}".format(args.batch_size)) + + elif args.request == "http": + raise ("Not support http service.") + end = time.time() + qps = itr * args.batch_size / (end - start) + return [[end - start, qps]] + + +if __name__ == '__main__': + multi_thread_runner = MultiThreadRunner() + endpoint_list = ["127.0.0.1:9292"] + #result = single_func(0, {"endpoint": endpoint_list}) + result = multi_thread_runner.run(single_func, args.thread, + {"endpoint": endpoint_list}) + print(result) + avg_cost = 0 + qps = 0 + for i in range(args.thread): + avg_cost += result[0][i * 2 + 0] + qps += result[0][i * 2 + 1] + avg_cost = avg_cost / args.thread + print("average total cost {} s.".format(avg_cost)) + print("qps {} ins/s".format(qps)) diff --git a/python/examples/criteo_ctr_with_cube/benchmark_batch.sh b/python/examples/criteo_ctr_with_cube/benchmark_batch.sh new file mode 100644 index 00000000..3a51c0de --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/benchmark_batch.sh @@ -0,0 +1,12 @@ +rm profile_log +for thread_num in 1 2 4 8 16 +do +for batch_size in 1 2 4 8 16 32 64 128 256 512 +do + $PYTHONROOT/bin/python benchmark_batch.py --thread $thread_num --batch_size $batch_size --model serving_client_conf/serving_client_conf.prototxt --request rpc > profile 2>&1 + echo "========================================" + echo "batch size : $batch_size" >> profile_log + $PYTHONROOT/bin/python ../util/show_profile.py profile $thread_num >> profile_log + tail -n 2 profile >> profile_log +done +done diff --git a/python/examples/criteo_ctr_with_cube/clean.sh b/python/examples/criteo_ctr_with_cube/clean.sh new file mode 100644 index 00000000..522a602a --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/clean.sh @@ -0,0 +1,5 @@ +ps -ef | grep cube | awk {'print $2'} | xargs kill -9 +ps -ef | grep SimpleHTTPServer | awk {'print $2'} | xargs kill -9 +rm -rf cube/cube_data cube/data cube/log* cube/nohup* cube/output/ cube/donefile cube/input cube/monitor cube/cube-builder.INFO +ps -ef | grep test | awk {'print $2'} | xargs kill -9 +ps -ef | grep serving | awk {'print $2'} | xargs kill -9 diff --git a/python/examples/criteo_ctr_with_cube/criteo.py b/python/examples/criteo_ctr_with_cube/criteo.py new file mode 100644 index 00000000..f37eb1d2 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/criteo.py @@ -0,0 +1,81 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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 sys + + +class CriteoDataset(object): + def setup(self, sparse_feature_dim): + self.cont_min_ = [0, -3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + self.cont_max_ = [ + 20, 600, 100, 50, 64000, 500, 100, 50, 500, 10, 10, 10, 50 + ] + self.cont_diff_ = [ + 20, 603, 100, 50, 64000, 500, 100, 50, 500, 10, 10, 10, 50 + ] + self.hash_dim_ = sparse_feature_dim + # here, training data are lines with line_index < train_idx_ + self.train_idx_ = 41256555 + self.continuous_range_ = range(1, 14) + self.categorical_range_ = range(14, 40) + + def _process_line(self, line): + features = line.rstrip('\n').split('\t') + dense_feature = [] + sparse_feature = [] + for idx in self.continuous_range_: + if features[idx] == '': + dense_feature.append(0.0) + else: + dense_feature.append((float(features[idx]) - self.cont_min_[idx - 1]) / \ + self.cont_diff_[idx - 1]) + for idx in self.categorical_range_: + sparse_feature.append( + [hash(str(idx) + features[idx]) % self.hash_dim_]) + + return dense_feature, sparse_feature, [int(features[0])] + + def infer_reader(self, filelist, batch, buf_size): + def local_iter(): + for fname in filelist: + with open(fname.strip(), "r") as fin: + for line in fin: + dense_feature, sparse_feature, label = self._process_line( + line) + #yield dense_feature, sparse_feature, label + yield [dense_feature] + sparse_feature + [label] + + import paddle + batch_iter = paddle.batch( + paddle.reader.shuffle( + local_iter, buf_size=buf_size), + batch_size=batch) + return batch_iter + + def generate_sample(self, line): + def data_iter(): + dense_feature, sparse_feature, label = self._process_line(line) + feature_name = ["dense_input"] + for idx in self.categorical_range_: + feature_name.append("C" + str(idx - 13)) + feature_name.append("label") + yield zip(feature_name, [dense_feature] + sparse_feature + [label]) + + return data_iter + + +if __name__ == "__main__": + criteo_dataset = CriteoDataset() + criteo_dataset.setup(int(sys.argv[1])) + criteo_dataset.run_from_stdin() diff --git a/python/examples/criteo_ctr_with_cube/criteo_reader.py b/python/examples/criteo_ctr_with_cube/criteo_reader.py new file mode 100644 index 00000000..2a80af78 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/criteo_reader.py @@ -0,0 +1,83 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +import sys +import paddle.fluid.incubate.data_generator as dg + + +class CriteoDataset(dg.MultiSlotDataGenerator): + def setup(self, sparse_feature_dim): + self.cont_min_ = [0, -3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + self.cont_max_ = [ + 20, 600, 100, 50, 64000, 500, 100, 50, 500, 10, 10, 10, 50 + ] + self.cont_diff_ = [ + 20, 603, 100, 50, 64000, 500, 100, 50, 500, 10, 10, 10, 50 + ] + self.hash_dim_ = sparse_feature_dim + # here, training data are lines with line_index < train_idx_ + self.train_idx_ = 41256555 + self.continuous_range_ = range(1, 14) + self.categorical_range_ = range(14, 40) + + def _process_line(self, line): + features = line.rstrip('\n').split('\t') + dense_feature = [] + sparse_feature = [] + for idx in self.continuous_range_: + if features[idx] == '': + dense_feature.append(0.0) + else: + dense_feature.append((float(features[idx]) - self.cont_min_[idx - 1]) / \ + self.cont_diff_[idx - 1]) + for idx in self.categorical_range_: + sparse_feature.append( + [hash(str(idx) + features[idx]) % self.hash_dim_]) + + return dense_feature, sparse_feature, [int(features[0])] + + def infer_reader(self, filelist, batch, buf_size): + def local_iter(): + for fname in filelist: + with open(fname.strip(), "r") as fin: + for line in fin: + dense_feature, sparse_feature, label = self._process_line( + line) + #yield dense_feature, sparse_feature, label + yield [dense_feature] + sparse_feature + [label] + + import paddle + batch_iter = paddle.batch( + paddle.reader.shuffle( + local_iter, buf_size=buf_size), + batch_size=batch) + return batch_iter + + def generate_sample(self, line): + def data_iter(): + dense_feature, sparse_feature, label = self._process_line(line) + feature_name = ["dense_input"] + for idx in self.categorical_range_: + feature_name.append("C" + str(idx - 13)) + feature_name.append("label") + yield zip(feature_name, [dense_feature] + sparse_feature + [label]) + + return data_iter + + +if __name__ == "__main__": + criteo_dataset = CriteoDataset() + criteo_dataset.setup(int(sys.argv[1])) + criteo_dataset.run_from_stdin() diff --git a/python/examples/criteo_ctr_with_cube/cube/conf/cube.conf b/python/examples/criteo_ctr_with_cube/cube/conf/cube.conf new file mode 100644 index 00000000..b70f6e34 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/cube/conf/cube.conf @@ -0,0 +1,13 @@ +[{ + "dict_name": "test_dict", + "shard": 1, + "dup": 1, + "timeout": 200, + "retry": 3, + "backup_request": 100, + "type": "ipport_list", + "load_balancer": "rr", + "nodes": [{ + "ipport_list": "list://127.0.0.1:8027" + }] +}] diff --git a/python/examples/criteo_ctr_with_cube/cube/conf/gflags.conf b/python/examples/criteo_ctr_with_cube/cube/conf/gflags.conf new file mode 100644 index 00000000..21c7bdde --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/cube/conf/gflags.conf @@ -0,0 +1,4 @@ +--port=8027 +--dict_split=1 +--in_mem=true +--log_dir=./log/ diff --git a/python/examples/criteo_ctr_with_cube/cube/conf/transfer.conf b/python/examples/criteo_ctr_with_cube/cube/conf/transfer.conf new file mode 100644 index 00000000..564d7a0d --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/cube/conf/transfer.conf @@ -0,0 +1,17 @@ +[default] +dict_name: test_dict +mode: base_only +download_mode: http +wget_port: 8098 +buildtool_local: /Serving/python/examples/criteo_ctr/cube/cube-builder +donefile_address: http://127.0.0.1:8080/output/ctr_cube/donefile +output_address: /Serving/python/examples/criteo_ctr/cube/output +tmp_address: /Serving/python/examples/criteo_ctr/cube/output +shard_num: 1 +copy_num: 1 +deploy_path: /Serving/python/examples/criteo_ctr/cube/test_dict +transfer_address: 127.0.0.1 + +[cube_agent] +agent0_0: 127.0.0.1:8001 +cube0_0: 127.0.0.1:8027:/Serving/python/examples/criteo_ctr/cube diff --git a/python/examples/criteo_ctr_with_cube/cube/keys b/python/examples/criteo_ctr_with_cube/cube/keys new file mode 100644 index 00000000..f00c965d --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/cube/keys @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/python/examples/criteo_ctr_with_cube/cube_prepare.sh b/python/examples/criteo_ctr_with_cube/cube_prepare.sh new file mode 100644 index 00000000..13753ee5 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/cube_prepare.sh @@ -0,0 +1,7 @@ + +mkdir -p cube_model +mkdir -p cube/data +./seq_generator ctr_serving_model/SparseFeatFactors ./cube_model/feature +/Serving/python/examples/criteo_ctr/cube/cube-builder -dict_name=test_dict -job_mode=base -last_version=0 -cur_version=0 -depend_version=0 -input_path=/Serving/python/examples/criteo_ctr/cube_model -output_path=/Serving/python/examples/criteo_ctr/cube/data -shard_num=1 -only_build=false +mv /Serving/python/examples/criteo_ctr/cube/data/0_0/test_dict_part0/* /Serving/python/examples/criteo_ctr/cube/data/ +cd cube && ./cube diff --git a/python/examples/criteo_ctr_with_cube/get_data.sh b/python/examples/criteo_ctr_with_cube/get_data.sh new file mode 100644 index 00000000..1f244b3a --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/get_data.sh @@ -0,0 +1,2 @@ +wget --no-check-certificate https://paddle-serving.bj.bcebos.com/data/ctr_prediction/ctr_data.tar.gz +tar -zxvf ctr_data.tar.gz diff --git a/python/examples/criteo_ctr_with_cube/local_train.py b/python/examples/criteo_ctr_with_cube/local_train.py new file mode 100644 index 00000000..745260da --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/local_train.py @@ -0,0 +1,100 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +from __future__ import print_function + +from args import parse_args +import os +import paddle.fluid as fluid +import sys +from network_conf import dnn_model + +dense_feature_dim = 13 + + +def train(): + args = parse_args() + sparse_only = args.sparse_only + if not os.path.isdir(args.model_output_dir): + os.mkdir(args.model_output_dir) + dense_input = fluid.layers.data( + name="dense_input", shape=[dense_feature_dim], dtype='float32') + sparse_input_ids = [ + fluid.layers.data( + name="C" + str(i), shape=[1], lod_level=1, dtype="int64") + for i in range(1, 27) + ] + label = fluid.layers.data(name='label', shape=[1], dtype='int64') + + #nn_input = None if sparse_only else dense_input + nn_input = dense_input + predict_y, loss, auc_var, batch_auc_var, infer_vars = dnn_model( + nn_input, sparse_input_ids, label, args.embedding_size, + args.sparse_feature_dim) + + optimizer = fluid.optimizer.SGD(learning_rate=1e-4) + optimizer.minimize(loss) + + exe = fluid.Executor(fluid.CPUPlace()) + exe.run(fluid.default_startup_program()) + dataset = fluid.DatasetFactory().create_dataset("InMemoryDataset") + dataset.set_use_var([dense_input] + sparse_input_ids + [label]) + + python_executable = "python" + pipe_command = "{} criteo_reader.py {}".format(python_executable, + args.sparse_feature_dim) + + dataset.set_pipe_command(pipe_command) + dataset.set_batch_size(128) + thread_num = 10 + dataset.set_thread(thread_num) + + whole_filelist = [ + "raw_data/part-%d" % x for x in range(len(os.listdir("raw_data"))) + ] + + print(whole_filelist) + dataset.set_filelist(whole_filelist[:thread_num]) + dataset.load_into_memory() + fluid.layers.Print(auc_var) + epochs = 1 + for i in range(epochs): + exe.train_from_dataset( + program=fluid.default_main_program(), dataset=dataset, debug=True) + print("epoch {} finished".format(i)) + + import paddle_serving_client.io as server_io + feed_var_dict = {} + feed_var_dict['dense_input'] = dense_input + for i, sparse in enumerate(sparse_input_ids): + feed_var_dict["embedding_{}.tmp_0".format(i)] = sparse + fetch_var_dict = {"prob": predict_y} + + feed_kv_dict = {} + feed_kv_dict['dense_input'] = dense_input + for i, emb in enumerate(infer_vars): + feed_kv_dict["embedding_{}.tmp_0".format(i)] = emb + fetch_var_dict = {"prob": predict_y} + + server_io.save_model("ctr_serving_model", "ctr_client_conf", feed_var_dict, + fetch_var_dict, fluid.default_main_program()) + + server_io.save_model("ctr_serving_model_kv", "ctr_client_conf_kv", + feed_kv_dict, fetch_var_dict, + fluid.default_main_program()) + + +if __name__ == '__main__': + train() diff --git a/python/examples/criteo_ctr_with_cube/network_conf.py b/python/examples/criteo_ctr_with_cube/network_conf.py new file mode 100644 index 00000000..2975533a --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/network_conf.py @@ -0,0 +1,77 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +import paddle.fluid as fluid +import math + + +def dnn_model(dense_input, sparse_inputs, label, embedding_size, + sparse_feature_dim): + def embedding_layer(input): + emb = fluid.layers.embedding( + input=input, + is_sparse=True, + is_distributed=False, + size=[sparse_feature_dim, embedding_size], + param_attr=fluid.ParamAttr( + name="SparseFeatFactors", + initializer=fluid.initializer.Uniform())) + x = fluid.layers.sequence_pool(input=emb, pool_type='sum') + return emb, x + + def mlp_input_tensor(emb_sums, dense_tensor): + #if isinstance(dense_tensor, fluid.Variable): + # return fluid.layers.concat(emb_sums, axis=1) + #else: + return fluid.layers.concat(emb_sums + [dense_tensor], axis=1) + + def mlp(mlp_input): + fc1 = fluid.layers.fc(input=mlp_input, + size=400, + act='relu', + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.Normal( + scale=1 / math.sqrt(mlp_input.shape[1])))) + fc2 = fluid.layers.fc(input=fc1, + size=400, + act='relu', + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.Normal( + scale=1 / math.sqrt(fc1.shape[1])))) + fc3 = fluid.layers.fc(input=fc2, + size=400, + act='relu', + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.Normal( + scale=1 / math.sqrt(fc2.shape[1])))) + pre = fluid.layers.fc(input=fc3, + size=2, + act='softmax', + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.Normal( + scale=1 / math.sqrt(fc3.shape[1])))) + return pre + + emb_pair_sums = list(map(embedding_layer, sparse_inputs)) + emb_sums = [x[1] for x in emb_pair_sums] + infer_vars = [x[0] for x in emb_pair_sums] + mlp_in = mlp_input_tensor(emb_sums, dense_input) + predict = mlp(mlp_in) + cost = fluid.layers.cross_entropy(input=predict, label=label) + avg_cost = fluid.layers.reduce_sum(cost) + accuracy = fluid.layers.accuracy(input=predict, label=label) + auc_var, batch_auc_var, auc_states = \ + fluid.layers.auc(input=predict, label=label, num_thresholds=2 ** 12, slide_steps=20) + return predict, avg_cost, auc_var, batch_auc_var, infer_vars diff --git a/python/examples/criteo_ctr_with_cube/test_client.py b/python/examples/criteo_ctr_with_cube/test_client.py new file mode 100644 index 00000000..effae405 --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/test_client.py @@ -0,0 +1,52 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +from paddle_serving_client import Client +import sys +import os +import criteo as criteo +import time +from paddle_serving_client.metric import auc + +client = Client() +client.load_client_config(sys.argv[1]) +client.connect(["127.0.0.1:9292"]) + +batch = 1 +buf_size = 100 +dataset = criteo.CriteoDataset() +dataset.setup(1000001) +test_filelists = [ + "{}/part-%d".format(sys.argv[2]) % x + for x in range(len(os.listdir(sys.argv[2]))) +] +reader = dataset.infer_reader(test_filelists[len(test_filelists) - 40:], batch, + buf_size) +label_list = [] +prob_list = [] +start = time.time() +for ei in range(1000): + data = reader().next() + feed_dict = {} + feed_dict['dense_input'] = data[0][0] + for i in range(1, 27): + feed_dict["embedding_{}.tmp_0".format(i - 1)] = data[0][i] + fetch_map = client.predict(feed=feed_dict, fetch=["prob"]) + prob_list.append(fetch_map['prob'][1]) + label_list.append(data[0][-1][0]) + +print(auc(label_list, prob_list)) +end = time.time() +print(end - start) diff --git a/python/examples/criteo_ctr_with_cube/test_server.py b/python/examples/criteo_ctr_with_cube/test_server.py new file mode 100644 index 00000000..6c2918ce --- /dev/null +++ b/python/examples/criteo_ctr_with_cube/test_server.py @@ -0,0 +1,36 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# 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. +# pylint: disable=doc-string-missing + +import os +import sys +from paddle_serving_server import OpMaker +from paddle_serving_server import OpSeqMaker +from paddle_serving_server import Server + +op_maker = OpMaker() +read_op = op_maker.create('general_reader') +general_dist_kv_infer_op = op_maker.create('general_dist_kv_infer') +response_op = op_maker.create('general_response') + +op_seq_maker = OpSeqMaker() +op_seq_maker.add_op(read_op) +op_seq_maker.add_op(general_dist_kv_infer_op) +op_seq_maker.add_op(response_op) + +server = Server() +server.set_op_sequence(op_seq_maker.get_op_sequence()) +server.load_model_config(sys.argv[1]) +server.prepare_server(workdir="work_dir1", port=9292, device="cpu") +server.run_server() diff --git a/python/paddle_serving_server/__init__.py b/python/paddle_serving_server/__init__.py index 87c6b496..55c5f8b4 100644 --- a/python/paddle_serving_server/__init__.py +++ b/python/paddle_serving_server/__init__.py @@ -32,6 +32,7 @@ class OpMaker(object): "general_text_reader": "GeneralTextReaderOp", "general_text_response": "GeneralTextResponseOp", "general_single_kv": "GeneralSingleKVOp", + "general_dist_kv_infer": "GeneralDistKVInferOp", "general_dist_kv": "GeneralDistKVOp", "general_copy": "GeneralCopyOp" } @@ -82,6 +83,7 @@ class Server(object): self.infer_service_fn = "infer_service.prototxt" self.model_toolkit_fn = "model_toolkit.prototxt" self.general_model_config_fn = "general_model.prototxt" + self.cube_config_fn = "cube.conf" self.workdir = "" self.max_concurrency = 0 self.num_threads = 4 @@ -157,6 +159,11 @@ class Server(object): "w") as fout: fout.write(str(self.model_conf)) self.resource_conf = server_sdk.ResourceConf() + for workflow in self.workflow_conf.workflows: + for node in workflow.nodes: + if "dist_kv" in node.name: + self.resource_conf.cube_config_path = workdir + self.resource_conf.cube_config_file = self.cube_config_fn self.resource_conf.model_toolkit_path = workdir self.resource_conf.model_toolkit_file = self.model_toolkit_fn self.resource_conf.general_model_path = workdir @@ -295,6 +302,6 @@ class Server(object): self.workdir, self.workflow_fn, self.num_threads) - print("Going to Run Comand") + print("Going to Run Command") print(command) os.system(command) -- GitLab