From bcc3ed39c4ca482f220ef9bc8624d57e62724880 Mon Sep 17 00:00:00 2001 From: Jihoon Lee Date: Fri, 5 Nov 2021 16:22:20 +0900 Subject: [PATCH] [Clean] Separate src spec from dep spec As source specification and dep specification is being deviated, this patch prepares for the further code changes with minor clearance. **Changes proposed in this PR:** - RequestSpec now has tensor, details - Detail is `std::variant` **Self evaluation:** 1. Build test: [X]Passed [ ]Failed [ ]Skipped 2. Run test: [X]Passed [ ]Failed [ ]Skipped Signed-off-by: Jihoon Lee --- nntrainer/tensor/tensor_pool.cpp | 210 +++++++++++++---------- nntrainer/tensor/tensor_pool.h | 125 ++++++++------ test/unittest/unittest_nntrainer_tensor_pool.cpp | 7 +- 3 files changed, 193 insertions(+), 149 deletions(-) diff --git a/nntrainer/tensor/tensor_pool.cpp b/nntrainer/tensor/tensor_pool.cpp index c5e99ce..f4f58c3 100644 --- a/nntrainer/tensor/tensor_pool.cpp +++ b/nntrainer/tensor/tensor_pool.cpp @@ -1,12 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Copyright (C) 2020 Parichay Kapoor + * Copyright (C) 2021 Parichay Kapoor * * @file tensor_pool.cpp - * @date 19 Aug 2020 + * @date 19 Aug 2021 * @brief This is TensorPool for all requested tensors * @see https://github.com/nnstreamer/nntrainer * @author Parichay Kapoor + * @author Jihoon Lee * @bug No known bugs except for NYI items * * @todo add checks for request/updates that finalize is not done @@ -22,6 +23,10 @@ namespace nntrainer { +/// lambda overload helper +template struct overloaded_ : Ts... { using Ts::operator()...; }; +template overloaded_(Ts...)->overloaded_; + /** * @brief Request tensor with the given spec * @@ -34,20 +39,9 @@ Tensor *TensorPool::requestTensor(const TensorDim &dim, TensorLifespan lifespan, const std::string &name, const Tensor::Initializer &init) { - if (name_map.find(name) != name_map.end()) - throw std::invalid_argument("Cannot request tensor with same name"); - - if (dim.getDataLen() == 0) - throw std::invalid_argument("Cannot request tensor with size 0"); - - if (name.empty()) - throw std::invalid_argument("Cannot request tensor with empty name"); - - pool.push_back({std::make_unique(dim, false, init, name), exec_order, - lifespan, 0, 0, false}); - name_map[name] = pool.size() - 1; - - return pool.back().tensor.get(); + return registerRequestSpec( + {std::make_unique(dim, false, init, name), + TensorPool::SourceDetails{0, lifespan, exec_order, {}}}); } /** @@ -77,9 +71,13 @@ Tensor *TensorPool::requestPrerequestedTensor( TensorLifespan lifespan, const std::string &name, const std::string &shared_name, const Tensor::Initializer &init, const unsigned int offset) { - auto &spec = getSourceSpec(shared_name); - unsigned adjusted_offset = pool[name_map.at(shared_name)].offset + offset; + unsigned adjusted_offset = + std::visit(overloaded_{[](const SourceDetails &s) { return 0u; }, + [](const DependentDetails &s) { return s.offset; }}, + pool[name_map.at(shared_name)].details); + adjusted_offset += offset; + NNTR_THROW_IF(spec.tensor->getDim().getDataLen() < adjusted_offset + dim.getDataLen(), std::invalid_argument) @@ -92,24 +90,16 @@ Tensor *TensorPool::requestPrerequestedTensor( spec.tensor->getInitializer() != init) throw std::invalid_argument("Request tensor initialization mismatch"); - /** - * cannot expand lifespan of zero lifespan tensor - * it works for externally allocated tensors as well - */ - if (spec.lifespan != TensorLifespan::UNMANAGED) { - spec.exec_order.insert(spec.exec_order.end(), exec_order.begin(), - exec_order.end()); - spec.lifespan = enum_class_or(spec.lifespan, lifespan); - } + expandLifespan(spec, exec_order, lifespan); + std::get(spec.details).dependents.push_back(pool.size()); - /** @note requestTensor invalidates spec reference */ - /// @note: for the dependent tensor, it only contains its own exec order, and - /// thus not contain the whole exec_order - Tensor *ret = requestTensor(dim, exec_order, lifespan, name, init); - pool.back().token = name_map[shared_name]; - pool.back().dependent = true; - pool.back().offset = adjusted_offset; - return ret; + /** @note below invalidates spec reference */ + /** @note in case of view of view, internal datastructure saves the src to + * view index, not view to view reference in order to flatten depth */ + auto parent_name = name_map.at(spec.tensor->getName()); + return registerRequestSpec( + {std::make_unique(dim, false, init, name), + TensorPool::DependentDetails{parent_name, adjusted_offset}}); } /** @@ -123,25 +113,25 @@ void TensorPool::finalize(const MemoryPlanner &planner, mem_pool.clear(); unsigned int bytes_requested = 0; for (auto &spec : pool) { - /** do not include dependent tensors in planning layout */ - if (spec.dependent || spec.exec_order.empty() || - spec.lifespan == TensorLifespan::UNMANAGED) + auto details = std::get_if(&spec.details); + if (!details || details->lifespan == TensorLifespan::UNMANAGED || + details->exec_order.empty()) { continue; - - spec.token = 0; + } + details->token = 0; /** 1. create the validity ranges for the all the requested tensors */ unsigned int validity_start = - *std::min_element(spec.exec_order.begin(), spec.exec_order.end()); + *std::min_element(details->exec_order.begin(), details->exec_order.end()); unsigned int validity_end = - *std::max_element(spec.exec_order.begin(), spec.exec_order.end()); + *std::max_element(details->exec_order.begin(), details->exec_order.end()); /** * use lifespan to update the validity. * if the validity is long term, the tensor must stay valid for the * complete duration. */ - if (isTensorLongTerm(spec.lifespan)) { + if (isTensorLongTerm(details->lifespan)) { validity_start = start_order; validity_end = end_order; } @@ -156,10 +146,10 @@ void TensorPool::finalize(const MemoryPlanner &planner, * 3. requestMemory for all the tensors and set their tokens * @note +1 is to make the validity_end exlusive in the interval range */ - spec.token = mem_pool.requestMemory(spec.tensor->bytes(), validity_start, - validity_end + 1); + details->token = mem_pool.requestMemory(spec.tensor->bytes(), + validity_start, validity_end + 1); #ifdef DEBUG - if (spec.token == 0) + if (details->token == 0) throw std::runtime_error("Received invalid token from memory pool"); #endif @@ -191,17 +181,12 @@ void TensorPool::allocate() { /** set the pointers using the token for all the tensors */ for (auto &spec : pool) { - /** get data for the tensors which were requested */ - if (!spec.dependent && spec.token > 0) { - NNTR_THROW_IF(spec.offset != 0, std::invalid_argument) - << "source spec has to have always offset of zero, but given " - << spec.offset << " for tensor: " << spec.tensor->getName(); - spec.tensor->setData(mem_pool.getMemory(spec.token), true); - } else if (spec.dependent) { - const auto &name = spec.tensor->getName(); - spec.tensor->setData(getSourceSpec(name).tensor->getData() + spec.offset); - /** initialize is intentionally skipped here */ + auto details = std::get_if(&spec.details); + if (!details || details->token == 0) { + continue; } + spec.tensor->setData(mem_pool.getMemory(details->token), true); + syncDependents(spec); } } @@ -217,56 +202,101 @@ void TensorPool::deallocate() { } } +const std::vector & +TensorPool::getExecutionOrder(const std::string &name) { + return std::get(getSourceSpec(name).details).exec_order; +} + /** * @brief Expand the lifespan of the tensor with the given name * */ -void TensorPool::expand_lifespan(const std::string &name, - TensorLifespan lifespan) { - if (name_map.find(name) == name_map.end()) - throw std::invalid_argument("Requested tensor not found"); - - int parent_spec_idx = name_map[name]; - while (pool[parent_spec_idx].dependent == true) - parent_spec_idx = pool[parent_spec_idx].token; +TensorPool::RequestSpec & +TensorPool::expandLifespan(const std::string &name, + const std::vector &exec_order, + TensorLifespan lifespan) { + auto &spec = getSourceSpec(name); + expandLifespan(spec, exec_order, lifespan); + return spec; +} - auto &spec = pool[parent_spec_idx]; +void TensorPool::expandLifespan(RequestSpec &spec, + const std::vector &exec_order, + TensorLifespan lifespan) { + auto &details = std::get(spec.details); + NNTR_THROW_IF((details.lifespan != TensorLifespan::UNMANAGED && + lifespan == TensorLifespan::UNMANAGED), + std::invalid_argument) + << "Extending to lifespan to unmanaged is not possible for name: " + << spec.tensor->getName(); - if (spec.lifespan != TensorLifespan::UNMANAGED) - throw std::invalid_argument("Cannot extend tensor lifespan from ZERO"); + if (details.lifespan != TensorLifespan::UNMANAGED) { + /// update only if lifespan is unmanaged + details.lifespan = + enum_class_or(details.lifespan, lifespan); + } + details.exec_order.insert(details.exec_order.end(), exec_order.begin(), + exec_order.end()); +} - spec.lifespan = enum_class_or(spec.lifespan, lifespan); +void TensorPool::syncDependents(const RequestSpec &spec) { + auto &dependents = std::get(spec.details).dependents; + for (auto &dep : dependents) { + auto &dep_spec = pool.at(dep); + auto offset = std::get(dep_spec.details).offset; + dep_spec.tensor->setData(spec.tensor->getData() + offset); + } } -/** - * @brief Expand the execution order of the tensor with the given name - * - */ -void TensorPool::expand_lifespan(const std::string &name, - const std::vector &exec_order) { - if (name_map.find(name) == name_map.end()) - throw std::invalid_argument("Requested tensor not found"); +Tensor *TensorPool::registerRequestSpec(RequestSpec &&spec) { + auto &name = spec.tensor->getName(); + if (name_map.find(name) != name_map.end()) + throw std::invalid_argument("Cannot request tensor with same name"); + + if (spec.tensor->empty()) + throw std::invalid_argument("Cannot request tensor with size 0"); - int parent_spec_idx = name_map[name]; - while (pool[parent_spec_idx].dependent == true) - parent_spec_idx = pool[parent_spec_idx].token; + if (name.empty()) + throw std::invalid_argument("Cannot request tensor with empty name"); + + pool.push_back(std::move(spec)); + name_map[name] = pool.size() - 1; - auto &spec = pool[parent_spec_idx]; - spec.exec_order.insert(spec.exec_order.end(), exec_order.begin(), - exec_order.end()); + return pool.back().tensor.get(); } TensorPool::RequestSpec &TensorPool::getSourceSpec(const std::string &name) { - unsigned parent_spec_idx; - try { - parent_spec_idx = name_map.at(name); - } catch (...) { - throw std::invalid_argument("finding spec idx failed, name: " + name); + RequestSpec *rs = &pool.at(name_map.at(name)); + while (auto dep_details = std::get_if(&rs->details)) { + rs = &pool.at(dep_details->parent_idx); } - while (pool[parent_spec_idx].dependent == true) - parent_spec_idx = pool[parent_spec_idx].token; - return pool.at(parent_spec_idx); + return *rs; +} + +void TensorPool::setExternalTensor(const std::string &name, const Tensor &t) { + auto &spec = getSourceSpec(name); + auto &details = std::get(spec.details); + NNTR_THROW_IF(details.lifespan != TensorLifespan::UNMANAGED, + std::invalid_argument) + << "Cannot set external tensor for non-zero lifespan for " << name; + + NNTR_THROW_IF(t.size() == 0 && t.getData(), std::invalid_argument) + << "Error: setting invalid external tensor size 0 for " << name; + + NNTR_THROW_IF(t.size() != 0 && t.size() < spec.tensor->size(), + std::invalid_argument) + << "Error: setting external tensor of smaller size for " << name; + + spec.tensor->setData(t.getData()); +} + +void TensorPool::updateExternalTensors() { + for (auto &spec : pool) { + if (std::holds_alternative(spec.details)) { + syncDependents(spec); + } + } } Tensor *TensorPool::placeholder(const std::string &name, const TensorDim &dim) { diff --git a/nntrainer/tensor/tensor_pool.h b/nntrainer/tensor/tensor_pool.h index 6310199..81d1eab 100644 --- a/nntrainer/tensor/tensor_pool.h +++ b/nntrainer/tensor/tensor_pool.h @@ -1,12 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Copyright (C) 2020 Parichay Kapoor + * Copyright (C) 2021 Parichay Kapoor * * @file tensor_pool.h * @date 18 Aug 2021 * @brief This is TensorPool for all requested tensors * @see https://github.com/nnstreamer/nntrainer * @author Parichay Kapoor + * @author Jihoon Lee * @bug No known bugs except for NYI items * */ @@ -18,6 +19,7 @@ #include #include #include +#include #include #include @@ -136,30 +138,11 @@ public: void deallocate(); /** - * @brief Expand the lifespan of the tensor with the given name - * - * @param name The name of the tensor - * @param lifespan The lifespan to be expanded to - */ - void expand_lifespan(const std::string &name, TensorLifespan lifespan); - - /** - * @brief Expand the execution order of the tensor with the given name - * - * @param name The name of the tensor - * @param exec_order The execution orders - */ - void expand_lifespan(const std::string &name, - const std::vector &exec_order); - - /** * @brief Get execution order for the given tensor * * @return The execution order of the tensor */ - const std::vector &getExecutionOrder(const std::string &name) { - return getSourceSpec(name).exec_order; - } + const std::vector &getExecutionOrder(const std::string &name); /** * @brief Get the maximum real memory requirement @@ -200,36 +183,14 @@ public: * * @note Update externally dependent tensors data ptrs from their parents */ - void setExternalTensor(const std::string &name, const Tensor &t) { - auto &spec = getSourceSpec(name); - - if (spec.lifespan != TensorLifespan::UNMANAGED) - throw std::invalid_argument( - "Cannot set external tensor for non-zero lifespan"); - - if (t.size() == 0 && t.getData()) - throw std::invalid_argument( - "Error: setting invalid external tensor size 0 for " + name); - - if (t.size() != 0 && t.size() < spec.tensor->size()) - throw std::invalid_argument( - "Error: setting external tensor of smaller size for " + name); - - spec.tensor->setData(t.getData()); - } + void setExternalTensor(const std::string &name, const Tensor &t); /** * @brief Update externally dependent tensors * * @note Update externally dependent tensors data ptrs from their parents */ - void updateExternalTensors() { - for (auto &spec : pool) - if (spec.dependent) { - auto &mother_spec = getSourceSpec(spec.tensor->getName()); - spec.tensor->setData(mother_spec.tensor->getData() + spec.offset); - } - } + void updateExternalTensors(); /** * @brief request placeholder which will be not managed by this tensor pool @@ -300,9 +261,7 @@ public: */ Tensor *extend(const std::string &name, const std::vector &exec_order, - TensorLifespan lifespan) { - return nullptr; - } + TensorLifespan lifespan); /** * @brief create a new tensor if tensor does not exist else return the tensor @@ -341,17 +300,34 @@ public: private: /** + * @brief Source tensor detailed specification + * + */ + struct SourceDetails { + unsigned int token; /**< memory token */ + TensorLifespan lifespan; /**< life span of the tensor */ + std::vector exec_order; /**< exec order */ + std::vector + dependents; /**< list of dependents to the source */ + }; + + /** + * @brief Dependent tensor detaild specification + * + */ + struct DependentDetails { + unsigned int parent_idx; /**< index to the parent */ + unsigned int offset; /**< elementwise offset */ + }; + + /** * @brief Spec for storing each request of tensor from tensor pool * @todo move tensor initialization from tensor class to RequestSpec */ struct RequestSpec { - std::unique_ptr tensor; /**< tensor object itself */ - std::vector exec_order; /**< tensor exec order list */ - TensorLifespan lifespan; /**< tensor lifespan */ - unsigned int token; /**< tensor memory token or index to source spec */ - unsigned int offset = - 0; /**< offset in element from the primary source spec */ - bool dependent = false; /**< if dependent on another tensor for memory */ + std::unique_ptr tensor; /**< tensor object itself */ + std::variant + details; /**< additional information by its kind */ }; /** @@ -372,6 +348,45 @@ private: RequestSpec &getSourceSpec(const std::string &name); /** + * @brief Expand the lifespan of the tensor with the given name + * + * @param name The name of the tensor + * @param exec_order The execution orders + * @param lifespan The lifespan to be expanded to + * @return source spec for the name + */ + RequestSpec &expandLifespan(const std::string &name, + const std::vector &exec_order, + TensorLifespan lifespan); + + /** + * @brief expand life span with execution time + * + * @param spec specification + * @param exec_order exec order + * @param lifespan life span + */ + void expandLifespan(RequestSpec &spec, + const std::vector &exec_order, + TensorLifespan lifespan); + + /** + * @brief sync dependent tensors from updated source tensor + * @note syncing starting from dependents of dependents is invalid and will + * throw. + * + * @param spec spec with source details to refer to. + */ + void syncDependents(const RequestSpec &spec); + + /** + * @brief register a spec after creation + * + * @param spec spec to register + */ + Tensor *registerRequestSpec(RequestSpec &&spec); + + /** * note: unordered_map is not directly used for pool to ensure initialization * of weights */ diff --git a/test/unittest/unittest_nntrainer_tensor_pool.cpp b/test/unittest/unittest_nntrainer_tensor_pool.cpp index 48d54b5..eb83767 100644 --- a/test/unittest/unittest_nntrainer_tensor_pool.cpp +++ b/test/unittest/unittest_nntrainer_tensor_pool.cpp @@ -127,10 +127,9 @@ TEST(TensorPool, request_mem_07_n) { nntrainer::TensorLifespan::UNMANAGED, "abc")); - EXPECT_THROW(pool.requestPrerequestedTensor( - nntrainer::TensorDim({1}), {}, - nntrainer::TensorLifespan::UNMANAGED, "abc1", "not_exist"), - std::invalid_argument); + EXPECT_ANY_THROW(pool.requestPrerequestedTensor( + nntrainer::TensorDim({1}), {}, nntrainer::TensorLifespan::UNMANAGED, "abc1", + "not_exist")); } /** -- 2.7.4