From 8ebc512d252398578afe406ef87ae096b96f8cd8 Mon Sep 17 00:00:00 2001 From: Zirui Wu Date: Tue, 9 Mar 2021 15:13:21 -0500 Subject: [PATCH] plugin infrastruture for mindddata add single element string tensor support for plugin tensor --- .../ccsrc/minddata/dataset/CMakeLists.txt | 3 + .../bindings/dataset/kernels/ir/bindings.cc | 14 +- .../ccsrc/minddata/dataset/core/tensor.cc | 2 +- .../minddata/dataset/include/constants.h | 2 +- .../minddata/dataset/kernels/CMakeLists.txt | 21 ++- .../dataset/kernels/ir/data/transforms_ir.cc | 14 ++ .../dataset/kernels/ir/data/transforms_ir.h | 22 +++ .../minddata/dataset/kernels/plugin_op.cc | 102 ++++++++++++ .../minddata/dataset/kernels/plugin_op.h | 62 +++++++ .../minddata/dataset/kernels/tensor_op.h | 1 + .../minddata/dataset/plugin/CMakeLists.txt | 7 + .../dataset/plugin/include/shared_include.h | 151 ++++++++++++++++++ .../minddata/dataset/plugin/plugin_loader.cc | 110 +++++++++++++ .../minddata/dataset/plugin/plugin_loader.h | 61 +++++++ .../dataset/plugin/shared_lib_util.cc | 36 +++++ .../minddata/dataset/plugin/shared_lib_util.h | 38 +++++ mindspore/ccsrc/minddata/dataset/util/task.cc | 2 +- mindspore/dataset/transforms/c_transforms.py | 30 +++- mindspore/dataset/transforms/validators.py | 21 ++- tests/ut/cpp/dataset/tensorshape_test.cc | 20 ++- 20 files changed, 692 insertions(+), 27 deletions(-) create mode 100644 mindspore/ccsrc/minddata/dataset/kernels/plugin_op.cc create mode 100644 mindspore/ccsrc/minddata/dataset/kernels/plugin_op.h create mode 100644 mindspore/ccsrc/minddata/dataset/plugin/CMakeLists.txt create mode 100644 mindspore/ccsrc/minddata/dataset/plugin/include/shared_include.h create mode 100644 mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.cc create mode 100644 mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.h create mode 100644 mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.cc create mode 100644 mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.h diff --git a/mindspore/ccsrc/minddata/dataset/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/CMakeLists.txt index fd40a5c615..fa07735421 100644 --- a/mindspore/ccsrc/minddata/dataset/CMakeLists.txt +++ b/mindspore/ccsrc/minddata/dataset/CMakeLists.txt @@ -75,6 +75,7 @@ add_subdirectory(engine) add_subdirectory(api) add_subdirectory(text) add_subdirectory(callback) +add_subdirectory(plugin) ###################################################################### add_dependencies(utils core) add_dependencies(kernels-image core) @@ -103,6 +104,7 @@ add_dependencies(kernels-ir core) add_dependencies(kernels-ir-data core) add_dependencies(kernels-ir-vision core) + if(ENABLE_ACL) add_dependencies(kernels-dvpp-image core dvpp-utils) endif() @@ -156,6 +158,7 @@ set(submodules $ $ $ + $ ) if(ENABLE_ACL) diff --git a/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/ir/bindings.cc b/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/ir/bindings.cc index abd157540b..9dfa250e73 100644 --- a/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/ir/bindings.cc +++ b/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/ir/bindings.cc @@ -19,9 +19,10 @@ #include "minddata/dataset/api/python/pybind_register.h" #include "minddata/dataset/core/global_context.h" -#include "minddata/dataset/kernels/py_func_op.h" +#include "minddata/dataset/kernels/data/no_op.h" #include "minddata/dataset/kernels/ir/data/transforms_ir.h" #include "minddata/dataset/kernels/ir/vision/vision_ir.h" +#include "minddata/dataset/kernels/py_func_op.h" namespace mindspore { namespace dataset { @@ -52,6 +53,17 @@ PYBIND_REGISTER(TensorOperation, 0, ([](const py::module *m) { py::arg("TensorOperation"); })); +PYBIND_REGISTER( + PluginOperation, 1, ([](const py::module *m) { + (void)py::class_>( + *m, "PluginOperation") + .def(py::init()); + })); + +PYBIND_REGISTER(NoOp, 1, ([](const py::module *m) { + (void)py::class_>(*m, "NoOp").def(py::init<>()); + })); + PYBIND_REGISTER( ComposeOperation, 1, ([](const py::module *m) { (void)py::class_>( diff --git a/mindspore/ccsrc/minddata/dataset/core/tensor.cc b/mindspore/ccsrc/minddata/dataset/core/tensor.cc index 1d77fc62be..ab96a789c8 100644 --- a/mindspore/ccsrc/minddata/dataset/core/tensor.cc +++ b/mindspore/ccsrc/minddata/dataset/core/tensor.cc @@ -154,7 +154,7 @@ Status Tensor::CreateFromMemory(const TensorShape &shape, const DataType &type, } RETURN_IF_NOT_OK((*out)->AllocateBuffer(length)); - int ret_code = memcpy_s((*out)->data_, length, src, length); + int ret_code = memcpy_ss((*out)->data_, length, src, length); CHECK_FAIL_RETURN_UNEXPECTED(ret_code == 0, "Failed to copy data into tensor."); return Status::OK(); diff --git a/mindspore/ccsrc/minddata/dataset/include/constants.h b/mindspore/ccsrc/minddata/dataset/include/constants.h index b8a1a51622..a288761081 100644 --- a/mindspore/ccsrc/minddata/dataset/include/constants.h +++ b/mindspore/ccsrc/minddata/dataset/include/constants.h @@ -91,7 +91,7 @@ inline void BitSet(uint32_t *bits, uint32_t bitMask) { *bits |= bitMask; } inline void BitClear(uint32_t *bits, uint32_t bitMask) { *bits &= (~bitMask); } -constexpr int32_t kDeMaxDim = std::numeric_limits::max(); // 2147483647 or 2^32 -1 +constexpr int64_t kDeMaxDim = std::numeric_limits::max(); constexpr int32_t kDeMaxRank = std::numeric_limits::max(); constexpr int64_t kDeMaxFreq = std::numeric_limits::max(); // 9223372036854775807 or 2^(64-1) constexpr int64_t kDeMaxTopk = std::numeric_limits::max(); diff --git a/mindspore/ccsrc/minddata/dataset/kernels/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/kernels/CMakeLists.txt index ee1f8ac9a4..3e4ea443a8 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/CMakeLists.txt +++ b/mindspore/ccsrc/minddata/dataset/kernels/CMakeLists.txt @@ -3,19 +3,24 @@ add_subdirectory(data) add_subdirectory(ir) file(GLOB_RECURSE _CURRENT_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc") set_property(SOURCE ${_CURRENT_SRC_FILES} PROPERTY COMPILE_DEFINITIONS SUBMODULE_ID=mindspore::SubModuleId::SM_MD) + + +set(COMMON_TENSOR_OPS + data/compose_op.cc + data/random_apply_op.cc + data/random_choice_op.cc + tensor_op.cc + plugin_op.cc + ) + if(ENABLE_PYTHON) add_library(kernels OBJECT - data/compose_op.cc - data/random_apply_op.cc - data/random_choice_op.cc + ${COMMON_TENSOR_OPS} c_func_op.cc py_func_op.cc - tensor_op.cc) + ) target_include_directories(kernels PRIVATE ${pybind11_INCLUDE_DIRS}) else() add_library(kernels OBJECT - data/compose_op.cc - data/random_apply_op.cc - data/random_choice_op.cc - tensor_op.cc) + ${COMMON_TENSOR_OPS}) endif() diff --git a/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.cc b/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.cc index 6ee48ea306..1fac0b8fcf 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.cc +++ b/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.cc @@ -39,8 +39,10 @@ #include "minddata/dataset/kernels/data/slice_op.h" #endif #include "minddata/dataset/kernels/data/type_cast_op.h" + #ifndef ENABLE_ANDROID #include "minddata/dataset/kernels/data/unique_op.h" +#include "minddata/dataset/kernels/plugin_op.h" #endif #include "minddata/dataset/kernels/ir/validators.h" @@ -271,6 +273,18 @@ Status TypeCastOperation::to_json(nlohmann::json *out_json) { Status UniqueOperation::ValidateParams() { return Status::OK(); } std::shared_ptr UniqueOperation::Build() { return std::make_shared(); } +Status PluginOperation::ValidateParams() { + std::string err_msg; + err_msg += lib_path_.empty() ? "lib_path is empty, please specify a path to .so file. " : ""; + err_msg += func_name_.empty() ? "func_name_ is empty, please specify function name to load." : ""; + if (!err_msg.empty()) { + RETURN_STATUS_SYNTAX_ERROR(err_msg); + } + return Status::OK(); +} +std::shared_ptr PluginOperation::Build() { + return std::make_shared(lib_path_, func_name_, user_args_); +} #endif } // namespace transforms } // namespace dataset diff --git a/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.h b/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.h index 37c22ee046..114a945d13 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.h +++ b/mindspore/ccsrc/minddata/dataset/kernels/ir/data/transforms_ir.h @@ -41,6 +41,7 @@ constexpr char kRandomApplyOperation[] = "RandomApply"; constexpr char kRandomChoiceOperation[] = "RandomChoice"; constexpr char kTypeCastOperation[] = "TypeCast"; constexpr char kUniqueOperation[] = "Unique"; +constexpr char kPluginOperation[] = "Plugin"; // Transform operations for performing data transformation. namespace transforms { @@ -264,7 +265,28 @@ class UniqueOperation : public TensorOperation { std::string Name() const override { return kUniqueOperation; } }; + +class PluginOperation : public TensorOperation { + public: + explicit PluginOperation(const std::string &lib_path, const std::string &func_name, const std::string &user_args) + : lib_path_(lib_path), func_name_(func_name), user_args_(user_args) {} + + ~PluginOperation() = default; + + std::shared_ptr Build() override; + + Status ValidateParams() override; + + std::string Name() const override { return kPluginOperation; } + + private: + std::string lib_path_; + std::string func_name_; + std::string user_args_; +}; + #endif + } // namespace transforms } // namespace dataset } // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/plugin_op.cc b/mindspore/ccsrc/minddata/dataset/kernels/plugin_op.cc new file mode 100644 index 0000000000..7f5a04c37d --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/plugin_op.cc @@ -0,0 +1,102 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 "minddata/dataset/kernels/plugin_op.h" + +#include "minddata/dataset/core/tensor.h" +#include "minddata/dataset/plugin/plugin_loader.h" + +namespace mindspore { +namespace dataset { + +errno_t memcpy_s_loop(uchar *dest, size_t destMax, const uchar *src, size_t count) { + int64_t step = 0; + while (count >= SECUREC_MEM_MAX_LEN) { + int ret_code = memcpy_s(dest + step * SECUREC_MEM_MAX_LEN, SECUREC_MEM_MAX_LEN, src + step * SECUREC_MEM_MAX_LEN, + SECUREC_MEM_MAX_LEN); + if (ret_code != 0) return ret_code; + count -= SECUREC_MEM_MAX_LEN; + step++; + } + return memcpy_s(dest + step * SECUREC_MEM_MAX_LEN, count, src + step * SECUREC_MEM_MAX_LEN, count); +} + +Status PluginOp::PluginToTensorRow(const std::vector &in_row, TensorRow *out_row) { + CHECK_FAIL_RETURN_UNEXPECTED(out_row != nullptr && out_row->empty(), "null/empty out_row received!"); + out_row->reserve(in_row.size()); + for (const auto &tensor : in_row) { + std::shared_ptr output; + DataType tp = DataType(tensor.type_); + CHECK_FAIL_RETURN_UNEXPECTED(tp.IsNumeric() && tp != DataType::DE_UNKNOWN, "Unsupported type: " + tensor.type_); + RETURN_IF_NOT_OK(Tensor::CreateFromMemory(TensorShape(tensor.shape_), tp, tensor.buffer_.data(), &output)); + out_row->emplace_back(output); + } + return Status::OK(); +} + +Status PluginOp::TensorRowToPlugin(const TensorRow &in_row, std::vector *out_row) { + CHECK_FAIL_RETURN_UNEXPECTED(out_row != nullptr && out_row->empty(), "null/empty out_row received!"); + out_row->resize(in_row.size()); + for (size_t ind = 0; ind < in_row.size(); ind++) { + plugin::Tensor &tensor = (*out_row)[ind]; + if (in_row[ind]->type().IsNumeric()) { + dsize_t buffer_size = in_row[ind]->SizeInBytes(); + tensor.buffer_.resize(buffer_size); + int ret_code = memcpy_s_loop(tensor.buffer_.data(), buffer_size, in_row[ind]->GetBuffer(), buffer_size); + CHECK_FAIL_RETURN_UNEXPECTED(ret_code == 0, "Failed to copy data into tensor."); + + } else { // string tensor, for now, only tensor with 1 string is supported! + CHECK_FAIL_RETURN_UNEXPECTED(in_row[ind]->shape().NumOfElements() == 1, + "String tensor with more than 1 element is not yet supported."); + // get the first and only string in this tensor + std::string str1(*(in_row[ind]->begin())); + tensor.buffer_.resize(str1.size()); + std::memcpy(tensor.buffer_.data(), str1.data(), str1.size()); + } + tensor.shape_ = in_row[ind]->shape().AsVector(); + tensor.type_ = in_row[ind]->type().ToString(); + } + return Status::OK(); +} + +Status PluginOp::Compute(const TensorRow &input, TensorRow *output) { + // Compute should quit if init fails. Error code has already been logged, no need to repeat + RETURN_IF_NOT_OK(init_code_); + std::vector in_row, out_row; + RETURN_IF_NOT_OK(TensorRowToPlugin(input, &in_row)); + plugin::Status rc = plugin_op_->Compute(&in_row, &out_row); + CHECK_FAIL_RETURN_UNEXPECTED(rc.IsOk(), rc.ToString()); + RETURN_IF_NOT_OK(PluginToTensorRow(out_row, output)); + return Status::OK(); +} + +PluginOp::PluginOp(const std::string &lib_path, const std::string &func_name, const std::string &user_args) + : lib_path_(lib_path), func_name_(func_name), user_args_(user_args) { + init_code_ = Init(); +} + +Status PluginOp::Init() { + plugin::PluginManagerBase *plugin; + RETURN_IF_NOT_OK(PluginLoader::GetInstance()->LoadPlugin(lib_path_, &plugin)); + // casting a void pointer to specific type + plugin_op_ = dynamic_cast(plugin->GetModule(func_name_)); + RETURN_UNEXPECTED_IF_NULL(plugin_op_); + plugin::Status rc = plugin_op_->ParseSerializedArgs(user_args_); + CHECK_FAIL_RETURN_UNEXPECTED(rc.IsOk(), rc.ToString()); + return Status::OK(); +} + +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/plugin_op.h b/mindspore/ccsrc/minddata/dataset/kernels/plugin_op.h new file mode 100644 index 0000000000..91ad45bb65 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/plugin_op.h @@ -0,0 +1,62 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_PLUGIN_OP_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_PLUGIN_OP_H_ +#include +#include +#include +#include + +#include "minddata/dataset/plugin/include/shared_include.h" + +#include "minddata/dataset/kernels/tensor_op.h" +#include "minddata/dataset/core/tensor.h" +#include "minddata/dataset/core/tensor_row.h" +#include "minddata/dataset/util/status.h" + +namespace mindspore { +namespace dataset { + +// a generalized plugin for TensorOp +class PluginOp : public TensorOp { + public: + PluginOp(const std::string &lib_path, const std::string &func_name, const std::string &user_args); + + ~PluginOp() = default; + + Status Compute(const TensorRow &input, TensorRow *output) override; + + Status Init(); // load plugin module + + std::string Name() const override { return kPluginOp; } + + // helper function to convert between plugin Tensor and MindData Tensor + static Status PluginToTensorRow(const std::vector &, TensorRow *); + + static Status TensorRowToPlugin(const TensorRow &, std::vector *); + + private: + Status init_code_; + plugin::TensorOp *plugin_op_; + std::string lib_path_; + std::string func_name_; + std::string user_args_; +}; + +} // namespace dataset +} // namespace mindspore + +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_PLUGIN_OP_H_ diff --git a/mindspore/ccsrc/minddata/dataset/kernels/tensor_op.h b/mindspore/ccsrc/minddata/dataset/kernels/tensor_op.h index 2f919bbe14..18337afeff 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/tensor_op.h +++ b/mindspore/ccsrc/minddata/dataset/kernels/tensor_op.h @@ -145,6 +145,7 @@ constexpr char kUniqueOp[] = "UniqueOp"; // other constexpr char kCFuncOp[] = "CFuncOp"; constexpr char kPyFuncOp[] = "PyFuncOp"; +constexpr char kPluginOp[] = "PluginOp"; constexpr char kNoOp[] = "NoOp"; // A class that does a computation on a Tensor diff --git a/mindspore/ccsrc/minddata/dataset/plugin/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/plugin/CMakeLists.txt new file mode 100644 index 0000000000..52afe928b3 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/plugin/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB _CURRENT_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc") +set_property(SOURCE ${_CURRENT_SRC_FILES} PROPERTY COMPILE_DEFINITIONS SUBMODULE_ID=mindspore::SubModuleId::SM_MD) + +add_library(md_plugin OBJECT + shared_lib_util.cc + plugin_loader.cc + ) diff --git a/mindspore/ccsrc/minddata/dataset/plugin/include/shared_include.h b/mindspore/ccsrc/minddata/dataset/plugin/include/shared_include.h new file mode 100644 index 0000000000..4405b58fa3 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/plugin/include/shared_include.h @@ -0,0 +1,151 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_INCLUDE_SHARED_INCLUDE_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_INCLUDE_SHARED_INCLUDE_H_ + +#include +#include +#include +#include +/*** + * This file is is complied with both MindData and plugin separately. Changing this file without compiling both + * projects could lead to undefined behaviors. + */ + +namespace mindspore { +namespace dataset { +namespace plugin { +// forward declares +class PluginManagerBase; +class MindDataManagerBase; +// any plugin module is expected to expose these two functions as the entry point + +/// \brief First handshake between plugin and MD. +/// \param[in] MindDataManagerBase, a pointer for callback functions. (plugin can call MD function) +/// \return status code, Status::OK() if function succeeds. +extern "C" PluginManagerBase *GetInstance(MindDataManagerBase *); + +/// \brief Definition of this function is expected to deallocate PluginManager +/// \return void +extern "C" void DestroyInstance(); + +/*** + * Tentative version rule for Plugin: X.Y.Z + * X, major version, increment when additional file is included or other major changes + * Y, minor version, increment when class/API are changed or other minor changes + * Z, patch version, increment when bug fix is introduced or other patches + */ +static constexpr char kSharedIncludeVersion[] = "0.5.6"; + +/*** + * All derived classes defined in plugin side needs to inherit from this. + */ +class PluginBase { + protected: + virtual ~PluginBase() noexcept = default; +}; + +/*** + * This class is used for callback. Functions defined in MindData can be exposed to plugin via this virtual class. + * All derived classes of this have their definition on MindData side. + */ +class MindDataBase { + protected: + virtual ~MindDataBase() noexcept = default; +}; + +/*** + * This is a simplified version of Status code. It intends to offer a simple return type. The syntax of + * this class is modelled after existing Status code. + */ +class Status : PluginBase { + public: + static Status OK() noexcept { return Status(); } + static Status ERROR(const std::string &msg) noexcept { return Status(msg); } + Status(const Status &) = default; + Status(Status &&) = default; + + // helper functions + bool IsOk() const noexcept { return success_; } + const std::string &ToString() const noexcept { return status_msg_; } + + private: + Status() noexcept : success_(true) {} + explicit Status(const std::string &msg) noexcept : success_(false), status_msg_(msg) {} + const bool success_; + const std::string status_msg_; +}; + +/*** + * This is the interface through which MindData interacts with external .so files. There can only be 1 instance of + * this class (hence the name Singleton) per so file. This class is the in-memory representation of each so file. + * GetModule() returns class that contains runtime logic (e.g. GDALDecode). Raw pointer is used so that PluginManager + * owns the lifetime of whatever objects it returns. MindData can not part-take in the memory management of plugin + * objects. PluginManager is expected to be destroyed when DestroyInstance() is called. + */ +class PluginManagerBase : public PluginBase { + public: + virtual std::string GetPluginVersion() noexcept = 0; + + virtual std::map> GetModuleNames() noexcept = 0; + + /// \brief return the module (e.g. a specific plugin tensor op) based on the module name. (names can be queried) + /// \return pointer to the module. returns nullptr if module doesn't exist. + virtual PluginBase *GetModule(const std::string &name) noexcept = 0; +}; + +/*** + * This class is used to get functions (e.g. Log) from MindData. + */ +class MindDataManagerBase : public MindDataBase { + public: + virtual MindDataBase *GetModule(const std::string &name) noexcept = 0; +}; + +/*** + * this struct is a Tensor in its simplest form, it is used to send Tensor data between MindData and Plugin. + */ +class Tensor : public PluginBase { + public: + std::vector buffer_; // contains the actual content of tensor + std::vector shape_; // shape of tensor, can be empty which means scalar + std::vector offsets_; // store the offsets for only string Tensor + std::string type_; // supported string literals "unknown", "bool", "int8", "uint8", "int16", "uint16", "int32", + // "uint32", "int64", "uint64", "float16", "float32", "float64", "string" +}; + +/*** + * This is plugin's TensorOp which resembles MindData's TensorOp. No exception is allowed. Each function needs to catch + * all the errors thrown by 3rd party lib and if recovery isn't possible, return false and log the error. if MindData + * sees an function returns false, it will quit immediately without any attempt to resolve the issue. + */ +class TensorOp : public PluginBase { + public: + /// \brief Parse input params for this op. This function will only be called once for the lifetime of this object. + /// \return status code, Status::OK() if function succeeds. + virtual Status ParseSerializedArgs(const std::string &) noexcept = 0; + + /// \brief Perform operation on in_row and return out_row + /// \param[in] in_row pointer to input tensor row + /// \param[out] out_row pointer to output tensor row + /// \return status code, Status::OK() if function succeeds. + virtual Status Compute(std::vector *in_row, std::vector *out_row) noexcept = 0; +}; + +} // namespace plugin +} // namespace dataset +} // namespace mindspore +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_INCLUDE_SHARED_INCLUDE_H_ diff --git a/mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.cc b/mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.cc new file mode 100644 index 0000000000..4320ee71c3 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.cc @@ -0,0 +1,110 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 "minddata/dataset/plugin/plugin_loader.h" +#include "minddata/dataset/plugin/shared_lib_util.h" +#include "mindspore/core/utils/log_adapter.h" + +namespace mindspore { +namespace dataset { + +PluginLoader *PluginLoader::GetInstance() noexcept { + static PluginLoader pl; + return &pl; +} + +PluginLoader::~PluginLoader() { + std::vector keys; + // get the keys from map, this is to avoid concurrent iteration and delete + std::transform(plugins_.begin(), plugins_.end(), std::back_inserter(keys), [](const auto &p) { return p.first; }); + for (std::string &key : keys) { + Status rc = UnloadPlugin(key); + MSLOG_IF(ERROR, rc.IsError(), mindspore::NoExceptionType) << rc.ToString(); + } +} + +// LoadPlugin() is NOT thread-safe. It is supposed to be called when Ops are being built. E.g. PluginOp should call this +// within constructor instead of in its Compute() which is parallel. +Status PluginLoader::LoadPlugin(const std::string &filename, plugin::PluginManagerBase **singleton_plugin) { + RETURN_UNEXPECTED_IF_NULL(singleton_plugin); + auto itr = plugins_.find(filename); + // return ok if this module is already loaded + if (itr != plugins_.end()) { + *singleton_plugin = itr->second.first; + return Status::OK(); + } + // Open the .so file + void *handle = SharedLibUtil::Load(filename); + CHECK_FAIL_RETURN_UNEXPECTED(handle != nullptr, "fail to load:" + filename + ".\n" + SharedLibUtil::ErrMsg()); + + // Load GetInstance function ptr from the so file, so needs to be compiled with -fPIC + void *func_handle = SharedLibUtil::FindSym(handle, "GetInstance"); + CHECK_FAIL_RETURN_UNEXPECTED(func_handle != nullptr, "fail to find GetInstance()\n" + SharedLibUtil::ErrMsg()); + + // cast the returned function ptr of type void* to the type of GetInstance + plugin::PluginManagerBase *(*get_instance)(plugin::MindDataManagerBase *) = + reinterpret_cast(func_handle); + RETURN_UNEXPECTED_IF_NULL(get_instance); + + *singleton_plugin = get_instance(nullptr); // call function ptr to get instance + RETURN_UNEXPECTED_IF_NULL(*singleton_plugin); + + std::string v1 = (*singleton_plugin)->GetPluginVersion(), v2(plugin::kSharedIncludeVersion); + + // Version check, if version are not the same, log the error and return fail + if (v1 != v2) { + std::string err_msg = "[Plugin Version Error] expected:" + v2 + ", received:" + v1 + " please recompile."; + if (SharedLibUtil::Close(handle) != 0) err_msg += ("\ndlclose() error, err_msg:" + SharedLibUtil::ErrMsg() + "."); + RETURN_STATUS_UNEXPECTED(err_msg); + } + + const std::map> module_names = (*singleton_plugin)->GetModuleNames(); + for (auto &p : module_names) { + std::string msg = "Plugin " + p.first + " has module:"; + MS_LOG(DEBUG) << std::accumulate(p.second.begin(), p.second.end(), msg, + [](const std::string &msg, const std::string &nm) { return msg + " " + nm; }); + } + + // save the name and handle + plugins_.insert({filename, {*singleton_plugin, handle}}); + return Status::OK(); +} + +Status PluginLoader::UnloadPlugin(const std::string &filename) { + auto itr = plugins_.find(filename); + RETURN_OK_IF_TRUE(itr == plugins_.end()); // return true if this plugin was never loaded or already removed + + void *func_handle = SharedLibUtil::FindSym(itr->second.second, "DestroyInstance"); + CHECK_FAIL_RETURN_UNEXPECTED(func_handle != nullptr, "fail to find DestroyInstance()\n" + SharedLibUtil::ErrMsg()); + + void (*destroy_instance)() = reinterpret_cast(func_handle); + RETURN_UNEXPECTED_IF_NULL(destroy_instance); + + destroy_instance(); + CHECK_FAIL_RETURN_UNEXPECTED(SharedLibUtil::Close(itr->second.second) == 0, + "dlclose() error: " + SharedLibUtil::ErrMsg()); + + plugins_.erase(filename); + return Status::OK(); +} + +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.h b/mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.h new file mode 100644 index 0000000000..e6e233ee74 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/plugin/plugin_loader.h @@ -0,0 +1,61 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_PLUGIN_LOADER_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_PLUGIN_LOADER_H_ + +#include +#include +#include + +#include "minddata/dataset/plugin/include/shared_include.h" +#include "minddata/dataset/util/status.h" +#include "mindspore/core/utils/log_adapter.h" + +namespace mindspore { +namespace dataset { + +// This class manages all MindData's plugins. It serves as the singleton that owns all plugins and bridge the gap +// between C++ RAII and C style functions +class PluginLoader { + public: + /// \brief Singleton getter, + /// \return pointer to PluginLoader + static PluginLoader *GetInstance() noexcept; + + PluginLoader() = default; + + /// \brief destructor, will call unload internally to unload all plugins managed by PluginLoader + ~PluginLoader(); + + /// \brief load an shared object (.so file) via dlopen() and return the ptr to the loaded file (singleton_plugin). + /// \param[in] filename the full path to .so file + /// \param[out] singleton_plugin pointer to the loaded file + /// \return status code + Status LoadPlugin(const std::string &filename, plugin::PluginManagerBase **singleton_plugin); + + private: + /// \brief Unload so file, internally will call dlclose() and delete its handle. + /// \param[in] filename, the full path to .so file + /// \return status code + Status UnloadPlugin(const std::string &filename); + + std::map> + plugins_; // key: path, val: plugin, dlopen handle +}; + +} // namespace dataset +} // namespace mindspore +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_PLUGIN_LOADER_H_ diff --git a/mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.cc b/mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.cc new file mode 100644 index 0000000000..6575f1cb89 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.cc @@ -0,0 +1,36 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 "minddata/dataset/plugin/shared_lib_util.h" +#ifdef __linux__ +#include +#endif +namespace mindspore { +namespace dataset { +#ifdef __linux__ +void *SharedLibUtil::Load(const std::string &name) { return dlopen(name.c_str(), RTLD_LAZY); } +void *SharedLibUtil::FindSym(void *handle, const std::string &name) { return dlsym(handle, name.c_str()); } +int32_t SharedLibUtil::Close(void *handle) { return dlclose(handle); } +std::string SharedLibUtil::ErrMsg() { return std::string(dlerror()); } +#else // MindData currently doesn't support loading shared library on platform that doesn't support dlopen +void *SharedLibUtil::Load(const std::string &name) { return nullptr; } +void *SharedLibUtil::FindSym(void *handle, const std::string &name) { return nullptr; } +int32_t SharedLibUtil::Close(void *handle) { return -1; } +std::string SharedLibUtil::ErrMsg() { return std::string("Plugin on non-Linux platform is not yet supported."); } +#endif + +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.h b/mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.h new file mode 100644 index 0000000000..9ae45f41bb --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/plugin/shared_lib_util.h @@ -0,0 +1,38 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_SHARED_LIB_UTIL_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_SHARED_LIB_UTIL_H_ + +#include + +namespace mindspore { +namespace dataset { + +// This class is a collection of util functions which aims at abstracting the dependency on OS +class SharedLibUtil { + public: + static void *Load(const std::string &name); + + static void *FindSym(void *handle, const std::string &name); + + static int32_t Close(void *handle); + + static std::string ErrMsg(); +}; + +} // namespace dataset +} // namespace mindspore +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_PLUGIN_SHARED_LIB_UTIL_H_ diff --git a/mindspore/ccsrc/minddata/dataset/util/task.cc b/mindspore/ccsrc/minddata/dataset/util/task.cc index 5fda685b43..2481c58230 100644 --- a/mindspore/ccsrc/minddata/dataset/util/task.cc +++ b/mindspore/ccsrc/minddata/dataset/util/task.cc @@ -64,7 +64,7 @@ void Task::operator()() { if (rc_.StatusCode() == StatusCode::kMDNetWorkError) { MS_LOG(WARNING) << rc_; } else { - MS_LOG(ERROR) << rc_; + MS_LOG(ERROR) << "Task: " << my_name_ << " is terminated with err msg: " << rc_; } ShutdownGroup(); } diff --git a/mindspore/dataset/transforms/c_transforms.py b/mindspore/dataset/transforms/c_transforms.py index 4b221c6014..bf0eca82a6 100644 --- a/mindspore/dataset/transforms/c_transforms.py +++ b/mindspore/dataset/transforms/c_transforms.py @@ -22,7 +22,7 @@ import mindspore.common.dtype as mstype import mindspore._c_dataengine as cde from .validators import check_num_classes, check_ms_type, check_fill_value, check_slice_option, check_slice_op, \ - check_mask_op, check_pad_end, check_concat_type, check_random_transform_ops + check_mask_op, check_pad_end, check_concat_type, check_random_transform_ops, check_plugin from ..core.datatypes import mstype_to_detype @@ -30,6 +30,7 @@ class TensorOperation: """ Base class Tensor Ops """ + def __call__(self, *input_tensor_list): tensor_row = [] for tensor in input_tensor_list: @@ -37,7 +38,7 @@ class TensorOperation: tensor_row.append(cde.Tensor(np.asarray(tensor))) except RuntimeError: raise TypeError("Invalid user input. Got {}: {}, cannot be converted into tensor." \ - .format(type(tensor), tensor)) + .format(type(tensor), tensor)) callable_op = cde.Execute(self.parse()) output_tensor_list = callable_op(tensor_row) for i, element in enumerate(output_tensor_list): @@ -392,6 +393,7 @@ class Unique(TensorOperation): >>> # +---------+-----------------+---------+ """ + def parse(self): return cde.UniqueOperation() @@ -474,3 +476,27 @@ class RandomChoice(TensorOperation): else: operations.append(op) return cde.RandomChoiceOperation(operations) + + +class Plugin(TensorOperation): + """ + Plugin support for MindData. Use this class to dynamically load a .so file (shared library) and execute its symbols. + + Args: + lib_path (str): Path to .so file which is compiled to support MindData plugin. + func_name (str): Name of the function to load from the .so file. + user_args (str, optional): Serialized args to pass to the plugin. Only needed if "func_name" requires one. + + Examples: + >>> plugin = c_transforms.Plugin("pluginlib.so", "PluginDecode") + >>> image_folder_dataset = image_folder_dataset.map(operations=plugin) + """ + + @check_plugin + def __init__(self, lib_path, func_name, user_args=None): + self.lib_path = lib_path + self.func_name = func_name + self.user_args = str() if (user_args is None) else user_args + + def parse(self): + return cde.PluginOperation(self.lib_path, self.func_name, self.user_args) diff --git a/mindspore/dataset/transforms/validators.py b/mindspore/dataset/transforms/validators.py index ce0211fc9d..a82748f722 100644 --- a/mindspore/dataset/transforms/validators.py +++ b/mindspore/dataset/transforms/validators.py @@ -272,7 +272,7 @@ def check_random_apply(method): for i, transform in enumerate(transforms): if str(transform).find("c_transform") >= 0: raise ValueError("transforms[{}] is not a py transforms. Should not use a c transform in py transform" \ - .format(i)) + .format(i)) if prob is not None: type_check(prob, (float, int,), "prob") @@ -294,7 +294,24 @@ def check_transforms_list(method): for i, transform in enumerate(transforms): if str(transform).find("c_transform") >= 0: raise ValueError("transforms[{}] is not a py transforms. Should not use a c transform in py transform" \ - .format(i)) + .format(i)) + return method(self, *args, **kwargs) + + return new_method + + +def check_plugin(method): + """Wrapper method to check the parameters of plugin.""" + + @wraps(method) + def new_method(self, *args, **kwargs): + [lib_path, func_name, user_args], _ = parse_user_args(method, *args, **kwargs) + + type_check(lib_path, (str,), "lib_path") + type_check(func_name, (str,), "func_name") + if user_args is not None: + type_check(user_args, (str,), "user_args") + return method(self, *args, **kwargs) return new_method diff --git a/tests/ut/cpp/dataset/tensorshape_test.cc b/tests/ut/cpp/dataset/tensorshape_test.cc index 7eec95b081..190f5158e4 100644 --- a/tests/ut/cpp/dataset/tensorshape_test.cc +++ b/tests/ut/cpp/dataset/tensorshape_test.cc @@ -27,16 +27,15 @@ namespace common = mindspore::common; using namespace mindspore::dataset; -using mindspore::MsLogLevel::INFO; -using mindspore::ExceptionType::NoExceptionType; using mindspore::LogStream; +using mindspore::ExceptionType::NoExceptionType; +using mindspore::MsLogLevel::INFO; class MindDataTestTensorShape : public UT::Common { public: - MindDataTestTensorShape() = default; + MindDataTestTensorShape() = default; }; - TEST_F(MindDataTestTensorShape, TestBasics) { std::vector vec = {4, 5, 6}; TensorShape t(vec); @@ -111,7 +110,7 @@ TEST_F(MindDataTestTensorShape, TestUnknown) { // Test materializing a TensorShape by calling method on a given column descriptor TEST_F(MindDataTestTensorShape, TestColDescriptor) { - int32_t rank = 0; // not used + int32_t rank = 0; // not used int32_t num_elements = 0; // Has no shape @@ -121,7 +120,7 @@ TEST_F(MindDataTestTensorShape, TestColDescriptor) { Status rc = c1.MaterializeTensorShape(num_elements, &generated_shape1); ASSERT_TRUE(rc.IsOk()); MS_LOG(INFO) << "generated_shape1: " << common::SafeCStr(generated_shape1.ToString()) << "."; - ASSERT_EQ(TensorShape({4}),generated_shape1); + ASSERT_EQ(TensorShape({4}), generated_shape1); // Has shape i.e. <*> TensorShape requested_shape2({TensorShape::kDimUnknown}); @@ -131,7 +130,7 @@ TEST_F(MindDataTestTensorShape, TestColDescriptor) { rc = c2.MaterializeTensorShape(num_elements, &generated_shape2); ASSERT_TRUE(rc.IsOk()); MS_LOG(INFO) << "generated_shape2: " << common::SafeCStr(generated_shape2.ToString()) << "."; - ASSERT_EQ(TensorShape({5}),generated_shape2); + ASSERT_EQ(TensorShape({5}), generated_shape2); // Compute unknown dimension <*,4> TensorShape requested_shape3({TensorShape::kDimUnknown, 4}); @@ -141,7 +140,7 @@ TEST_F(MindDataTestTensorShape, TestColDescriptor) { rc = c3.MaterializeTensorShape(num_elements, &generated_shape3); ASSERT_TRUE(rc.IsOk()); MS_LOG(INFO) << "generated_shape3: " << common::SafeCStr(generated_shape3.ToString()) << "."; - ASSERT_EQ(TensorShape({3,4}),generated_shape3); + ASSERT_EQ(TensorShape({3, 4}), generated_shape3); // Compute unknown dimension <3,*,4> TensorShape requested_shape4({3, TensorShape::kDimUnknown, 4}); @@ -151,7 +150,7 @@ TEST_F(MindDataTestTensorShape, TestColDescriptor) { rc = c4.MaterializeTensorShape(num_elements, &generated_shape4); ASSERT_TRUE(rc.IsOk()); MS_LOG(INFO) << "generated_shape4: " << common::SafeCStr(generated_shape4.ToString()) << "."; - ASSERT_EQ(TensorShape({3,2,4}),generated_shape4); + ASSERT_EQ(TensorShape({3, 2, 4}), generated_shape4); // requested and generated should be the same! <2,3,4> TensorShape requested_shape5({2, 3, 4}); @@ -161,7 +160,7 @@ TEST_F(MindDataTestTensorShape, TestColDescriptor) { rc = c5.MaterializeTensorShape(num_elements, &generated_shape5); ASSERT_TRUE(rc.IsOk()); MS_LOG(INFO) << "generated_shape5: " << common::SafeCStr(generated_shape5.ToString()) << "."; - ASSERT_EQ(requested_shape5,generated_shape5); + ASSERT_EQ(requested_shape5, generated_shape5); // expect fail due to multiple unknown dimensions TensorShape requested_shape6({2, TensorShape::kDimUnknown, TensorShape::kDimUnknown}); @@ -181,6 +180,5 @@ TEST_F(MindDataTestTensorShape, TestColDescriptor) { } TEST_F(MindDataTestTensorShape, TestInvalid) { - ASSERT_EQ(TensorShape({2147483648}), TensorShape::CreateUnknownRankShape()); ASSERT_EQ(TensorShape({kDeMaxDim - 1, kDeMaxDim - 1, kDeMaxDim - 1}), TensorShape::CreateUnknownRankShape()); }