try {
in_tensor = nntrainer::Tensor(inbatch);
mainNet.forwarding({MAKE_SHARED_TENSOR(in_tensor)}, {Q});
- mainNet.backwarding({Q}, iter);
+ mainNet.backwarding(iter);
} catch (...) {
std::cerr << "Error during backwarding the network" << std::endl;
return -1;
return;
this->batch_size = batch_size;
- if (!input_list.empty() and input_list[0]->getDim().batch() == batch_size)
+ if (!input_list.empty() && getInputDimension()[0].batch() == batch_size)
return;
auto allocated = tensor_manager->isAllocated();
* In-Place optimizations
*/
std::vector<std::string> inputs_name;
- if (lnode->getType() == FlattenLayer::type)
+ if (lnode->getType() == FlattenLayer::type ||
+ lnode->getType() == InputLayer::type)
std::transform(inputs.begin(), inputs.end(),
std::back_inserter(inputs_name),
[](const Var_Grad *val) { return val->getName(); });
* running the graph.
*/
if (gnode.getInputConnections().empty())
- input_list.insert(input_list.end(), inputs.begin(), inputs.end());
+ std::transform(inputs.begin(), inputs.end(), std::back_inserter(input_list),
+ [](auto const &val) { return val->getName(); });
/** @todo check compatibility of requireLabel() and
* getOutputConnections().empty() */
if (lnode->requireLabel())
- label_list.insert(label_list.end(), outputs.begin(), outputs.end());
+ std::transform(outputs.begin(), outputs.end(),
+ std::back_inserter(label_list),
+ [](auto const &val) { return val->getGradientName(); });
lnode->configureRunContext(
// TODO: update weights spec for trainable based on layer trainable prop
return status;
}
+void NetworkGraph::setExternalTensors(const std::vector<Tensor> &data,
+ const std::vector<std::string> names) {
+
+ /// feed or clear label
+ for (unsigned int idx = 0; idx < names.size(); idx++) {
+ if (data.empty())
+ tensor_manager->setExternalTensor(names[idx], Tensor());
+ else if (data.size() == 1)
+ tensor_manager->setExternalTensor(names[idx], data[0]);
+ else
+ tensor_manager->setExternalTensor(names[idx], data[idx]);
+ }
+}
+
+void NetworkGraph::setInputsLabels(const std::vector<Tensor> &inputs,
+ const std::vector<Tensor> &labels) {
+
+ NNTR_THROW_IF(labels.size() > 1 && labels.size() != label_list.size(),
+ std::invalid_argument)
+ << "label size does not match with the network requirements"
+ << " label size: " << labels.size()
+ << " requirements size: " << label_list.size();
+
+ NNTR_THROW_IF(inputs.size() > 1 && inputs.size() != input_list.size(),
+ std::invalid_argument)
+ << "input size does not match with the network requirements"
+ << " input size: " << inputs.size()
+ << " requirements size: " << input_list.size();
+
+ setExternalTensors(inputs, input_list);
+ setExternalTensors(labels, label_list);
+ tensor_manager->updateExternalTensors();
+}
+
+void NetworkGraph::setInputsLabels(sharedConstTensors &inputs,
+ sharedConstTensors &labels) {
+
+ std::vector<Tensor> ins;
+ std::transform(inputs.begin(), inputs.end(), std::back_inserter(ins),
+ [](auto const &val) { return *val.get(); });
+
+ std::vector<Tensor> labs;
+ std::transform(labels.begin(), labels.end(), std::back_inserter(labs),
+ [](auto const &val) { return *val.get(); });
+
+ setInputsLabels(ins, labs);
+}
+
} /* namespace nntrainer */
}
/**
- * @brief Get the Input List for the graph
+ * @brief Feed inputs and labels to the graph
*
- * @return const std::vector<Var_Grad *>& lists of inputs
+ * @param inputs Input data
+ * @param labels Label data
*/
- const std::vector<Var_Grad *> &getInputList() { return input_list; };
+ void setInputsLabels(const std::vector<Tensor> &inputs,
+ const std::vector<Tensor> &labels);
/**
- * @brief Get the Label List for the graph
+ * @brief Feed inputs and labels to the graph
*
- * @return const std::vector<Var_Grad *>& lists of labels
+ * @param inputs Input data
+ * @param labels Label data
*/
- const std::vector<Var_Grad *> &getLabelList() { return label_list; };
+ void setInputsLabels(sharedConstTensors &inputs, sharedConstTensors &labels);
private:
std::map<std::string, std::string> sub_in_out; /** This is map to identify
at the start of the graph */
bool compiled; /**< if the model graph is compiled */
unsigned int batch_size; /**< current batch_size */
- std::vector<Var_Grad *> label_list; /**< var_grads for the labels */
- std::vector<Var_Grad *> input_list; /**< var_grads for the inputs */
+ // std::vector<Var_Grad *> label_list; /**< var_grads for the labels */
+ // std::vector<Var_Grad *> input_list; /**< var_grads for the inputs */
+ std::vector<std::string> label_list; /**< var_grads for the labels */
+ std::vector<std::string> input_list; /**< var_grads for the inputs */
ExecutionMode exec_mode; /**< execution mode with which the graph has been
currently set or previously set */
* is expected to be called right after calcGradient().
*/
void setExecutionOrder();
+
+ /**
+ * @brief Set external data to the given tensors with name
+ *
+ * @param data External data
+ * @param names Names of the tensor to set the data to
+ */
+ void setExternalTensors(const std::vector<Tensor> &data,
+ const std::vector<std::string> names);
};
} // namespace nntrainer
void InputLayer::forwarding(RunLayerContext &context, bool training) {
Tensor &hidden_ = context.getOutput(SINGLE_INOUT_IDX);
- // hidden_ = context.getInput(SINGLE_INOUT_IDX);
- hidden_.copy(context.getInput(SINGLE_INOUT_IDX));
if (std::get<props::Normalization>(input_props))
hidden_.normalization_i();
*/
NeuralNetwork::~NeuralNetwork() = default;
-static void setLabels(const std::vector<Tensor> &data,
- const std::vector<Var_Grad *> &label_list) {
-
- NNTR_THROW_IF(data.size() > 1 && data.size() != label_list.size(),
- std::invalid_argument)
- << "label size does not match with the network requirements"
- << " label size: " << data.size()
- << " requirements size: " << label_list.size();
-
- /// feed or clear label
- for (unsigned int idx = 0; idx < label_list.size(); idx++) {
- if (data.empty())
- label_list[idx]->initializeGradient();
- else if (data.size() == 1)
- label_list[idx]->initializeGradient(data[0]);
- else
- label_list[idx]->initializeGradient(data[idx]);
- }
-}
-
-static void setInputs(const std::vector<Tensor> &data,
- const std::vector<Var_Grad *> &input_list) {
-
- NNTR_THROW_IF(data.size() > 1 && data.size() != input_list.size(),
- std::invalid_argument)
- << "input size does not match with the network requirements"
- << " input size: " << data.size()
- << " requirements size: " << input_list.size();
-
- /// feed or clear label
- for (unsigned int idx = 0; idx < input_list.size(); idx++) {
- if (data.empty())
- input_list[idx]->initializeVariable();
- else
- input_list[idx]->initializeVariable(data[idx]);
- }
-}
-
-static void setLabels(sharedConstTensors &data,
- const std::vector<Var_Grad *> &label_list) {
-
- std::vector<Tensor> labels;
- std::transform(data.begin(), data.end(), std::back_inserter(labels),
- [](auto const &val) { return *val.get(); });
-
- setLabels(labels, label_list);
-}
-
-static void setInputs(sharedConstTensors &data,
- const std::vector<Var_Grad *> &input_list) {
-
- std::vector<Tensor> inputs;
- std::transform(data.begin(), data.end(), std::back_inserter(inputs),
- [](auto const &val) { return *val.get(); });
-
- setInputs(inputs, input_list);
-}
-
/**
* @brief forward propagation using layers object which has layer
*/
<< " label_batch: " << label[0]->batch()
<< " target_batch: " << current_batch;
- setLabels(label, model_graph.getLabelList());
- setInputs(input, model_graph.getInputList());
+ model_graph.setInputsLabels(input, label);
return forwarding(training);
}
#endif
}
-/**
- * @brief back propagation
- * Call backwarding function of layer in reverse order
- * No need to call at first Input Layer (No data to be updated)
- */
-void NeuralNetwork::backwarding(sharedConstTensors label, int iteration) {
- setLabels(label, model_graph.getLabelList());
- backwarding(iteration);
-}
-
void NeuralNetwork::save(const std::string &file_path,
ml::train::ModelFormat format) {
NNTR_THROW_IF(!initialized, std::runtime_error)
model_graph.deallocateTensors(false);
/** Clear the set inputs and labels */
- setLabels({}, model_graph.getLabelList());
- setInputs({}, model_graph.getInputList());
+ model_graph.setInputsLabels({}, {});
return out;
}
}
auto const &labels = iteration.getLabelsRef();
- setLabels(labels, model_graph.getLabelList());
auto const &inputs = iteration.getInputsRef();
- setInputs(inputs, model_graph.getInputList());
+ model_graph.setInputsLabels(inputs, labels);
on_iteration_fetch(stat, *buffer);
on_iteration_update_stat(stat, {output}, labels);
}
/** Clear the set inputs and labels */
- setLabels({}, model_graph.getLabelList());
- setInputs({}, model_graph.getInputList());
+ model_graph.setInputsLabels({}, {});
return status;
}
sharedConstTensors label = {},
bool training = true);
- /**
- * @brief Backward Propagation of the neural network
- * @param[in] label List of Label Tensors for the model
- * @param[in] iteration Iteration Number for the optimizer
- */
- void backwarding(sharedConstTensors label, int iteration);
-
/**
* @brief Backward Propagation of the neural network
* @param[in] iteration Iteration Number for the optimizer
Tensor::Initializer::ZEROS /// tensor initializer
);
} else if (!node.getInputConnections().empty()) {
- /** skip requesting tensor for input */
var = tensor_pool.requestTensor(
dim, /// tensor dim
var_exec_order, var_ls,
var_name + Var_Grad::grad_suffix, /// name
Tensor::Initializer::ZEROS /// tensor initializer
);
+ } else {
+ /** requesting externally allocated tensor for input */
+ var = tensor_pool.requestExternallyAllocateTensor(
+ dim, /// tensor dim
+ var_name, /// name
+ Tensor::Initializer::NONE /// tensor initializer
+ );
+
+#ifdef ENABLE_TEST
+ grad = tensor_pool.requestTensor(
+ dim, /// tensor dim
+ grad_exec_order, grad_ls,
+ var_name + Var_Grad::grad_suffix, /// name
+ Tensor::Initializer::ZEROS /// tensor initializer
+ );
+#else
+ grad = tensor_pool.requestExternallyAllocateTensor(
+ dim, /// tensor dim
+ var_name + Var_Grad::grad_suffix, /// name
+ Tensor::Initializer::ZEROS /// tensor initializer
+ );
+#endif
}
/**
);
/** skip requesting tensor for label */
- if (!node.getOutputConnections().empty())
+ if (!node.getOutputConnections().empty()) {
grad = tensor_pool.requestPrerequestedTensor(
dim, /// tensor dim
grad_exec_order, grad_ls,
inputs_name[idx] + Var_Grad::grad_suffix, /// shared name
Tensor::Initializer::ZEROS /// tensor initializer
);
+ } else {
+ /** requesting externally allocated tensor for label */
+ grad = tensor_pool.requestExternallyAllocateTensor(
+ dim, /// tensor dim
+ var_name + Var_Grad::grad_suffix, /// name
+ Tensor::Initializer::ZEROS /// tensor initializer
+ );
+ }
} else {
var = tensor_pool.requestTensor(
dim, /// tensor dim
Tensor::Initializer::NONE /// tensor initializer
);
- /** skip requesting tensor for label */
- if (!node.getOutputConnections().empty())
+ if (!node.getOutputConnections().empty()) {
grad = tensor_pool.requestTensor(
dim, /// tensor dim
grad_exec_order, grad_ls,
var_name + Var_Grad::grad_suffix, /// name
Tensor::Initializer::ZEROS /// tensor initializer
);
+ } else {
+ /** requesting externally allocated tensor for label */
+ grad = tensor_pool.requestExternallyAllocateTensor(
+ dim, /// tensor dim
+ var_name + Var_Grad::grad_suffix, /// name
+ Tensor::Initializer::ZEROS /// tensor initializer
+ );
+ }
}
outputs_v2.emplace_back(std::make_unique<Var_Grad>(var, grad));
*/
void setOptimizations(bool val) { enable_optimizations = val; }
+ /**
+ * @brief Update the dependency on external tensors
+ */
+ void updateExternalTensors() {
+ weight_pool.updateExternalTensors();
+ tensor_pool.updateExternalTensors();
+ }
+
+ /**
+ * @brief Update externally dependent tensors
+ *
+ * @param name Name of the tensor
+ * @param t External tensor
+ */
+ void setExternalTensor(const std::string &name, const Tensor &t) {
+ tensor_pool.setExternalTensor(name, t);
+ }
+
private:
/** @todo: merge this list to one */
std::vector<std::unique_ptr<Weight>>
return pool.back().tensor.get();
}
+/**
+ * @brief Request tensor with the given spec
+ *
+ * @note returns empty tensor which will be filled when allocate is called.
+ * @note we assume that the caller checks if the exec_order and lifespan are
+ * compatible.
+ */
+Tensor *
+TensorPool::requestExternallyAllocateTensor(const TensorDim &dim,
+ const std::string &name,
+ const Tensor::Initializer &init) {
+ return requestTensor(dim, {}, TensorLifespan::ZERO_LIFESPAN, name, init);
+}
+
/**
* @brief Request tensor which has been already requested with the given
* spec
spec.tensor->getInitializer() != init)
throw std::invalid_argument("Request tensor initialization mismatch");
- spec.exec_order.insert(spec.exec_order.end(), exec_order.begin(),
- exec_order.end());
- spec.lifespan = enum_class_or<TensorLifespan>(spec.lifespan, lifespan);
+ /**
+ * cannot expand lifespan of zero lifespan tensor
+ * it works for externally allocated tensors as well
+ */
+ if (spec.lifespan != TensorLifespan::ZERO_LIFESPAN) {
+ spec.exec_order.insert(spec.exec_order.end(), exec_order.begin(),
+ exec_order.end());
+ spec.lifespan = enum_class_or<TensorLifespan>(spec.lifespan, lifespan);
+ }
/** @note requestTensor invalidates spec reference */
Tensor *ret = requestTensor(dim, exec_order, lifespan, name, init);
unsigned int bytes_requested = 0;
for (auto &spec : pool) {
/** do not include dependent tensors in planning layout */
- if (spec.dependent || spec.exec_order.empty())
+ if (spec.dependent || spec.exec_order.empty() ||
+ spec.lifespan == TensorLifespan::ZERO_LIFESPAN)
continue;
spec.token = 0;
parent_spec_idx = pool[parent_spec_idx].token;
auto &spec = pool[parent_spec_idx];
+
+ if (spec.lifespan != TensorLifespan::ZERO_LIFESPAN)
+ throw std::invalid_argument("Cannot extend tensor lifespan from ZERO");
+
spec.lifespan = enum_class_or<TensorLifespan>(spec.lifespan, lifespan);
}
TensorLifespan lifespan, const std::string &name,
const Tensor::Initializer &init = Tensor::Initializer::NONE);
+ /**
+ * @brief Request tensor with the given name which will be allocated
+ * externally
+ *
+ * @param dim Tensor dimensions
+ * @param name Name of this tensor
+ * @param init Initializer of the tensor
+ *
+ * @return ptr to the created tensor
+ *
+ * @note returns empty tensor which must be filled by the caller before use.
+ */
+ Tensor *requestExternallyAllocateTensor(
+ const TensorDim &dim, const std::string &name,
+ const Tensor::Initializer &init = Tensor::Initializer::NONE);
+
/**
* @brief Request tensor which has been already requested with the given
* spec
return pool[name_map.at(name)].tensor.get();
}
+ /**
+ * @brief Update externally dependent tensors
+ *
+ * @param name Name of the tensor
+ * @param t External tensor
+ *
+ * @note Update externally dependent tensors data ptrs from their parents
+ */
+ void setExternalTensor(const std::string &name, const Tensor &t) {
+ auto &spec = pool[name_map.at(name)];
+ if (spec.lifespan != TensorLifespan::ZERO_LIFESPAN)
+ throw std::invalid_argument(
+ "Cannot set external tensor for non-zero lifespan");
+
+ spec.tensor->setData(t.getData());
+ }
+
+ /**
+ * @brief Update externally dependent tensors
+ *
+ * @note Update externally dependent tensors data ptrs from their parents
+ */
+ void updateExternalTensors() {
+ for (auto &spec : pool)
+ if (spec.dependent)
+ spec.tensor->setData(pool[spec.token].tensor->getData());
+ }
+
private:
/**
* @brief Spec for storing each request of tensor from tensor pool
var->setData(preallocated.getData());
else
var = std::make_shared<Tensor>(preallocated);
+ // var = std::make_shared<Tensor>(preallocated);
/** intentionally not initialized tensor memory for shared tensors */
}
graph->addLayer(layer);
}
+ graph->setMemoryOptimizations(false);
return graph;
}
auto flatten = LayerRepresentation("flatten", {"name=flat"});
#ifdef ENABLE_TFLITE_INTERPRETER
-TEST(nntrainerInterpreterTflite, simple_fc) {
+
+/**
+ * TODO: update tflite interpreter after the change of semantics that tensors
+ * are different between input and output of a layer but the underlying data
+ * is same. Once the interpreter is updated, this test can be enabled.
+ */
+TEST(nntrainerInterpreterTflite, DISABLED_simple_fc) {
nntrainer::TfliteInterpreter interpreter;
nntrainer::Tensor ans(nntrainer::TensorDim({1, 1, 1, 2}));
ans.setValue(7.0f);
- /**
- * TODO: update tflite interpreter after the change of semantics that tensors
- * are different between input and output of a layer but the underlying data
- * is same. Once the interpreter is updated, this test can be enabled.
- */
- // EXPECT_EQ(out, ans);
+ EXPECT_EQ(out, ans);
if (remove("test.tflite")) {
std::cerr << "remove ini "
output.setValue(i, 0, 0, 3, 1.0);
NN.forwarding({MAKE_SHARED_TENSOR(input)}, {MAKE_SHARED_TENSOR(output)});
- NN.backwarding({MAKE_SHARED_TENSOR(output)}, 1);
+ NN.backwarding(1);
}
static nntrainer::IniSection nw_base("model", "Type = NeuralNetwork | "
it->forward(iteration, true);
if (loss_nodes.size()) {
- nn.backwarding(label, iteration);
+ nn.backwarding(iteration);
for (auto it = nodes.rbegin(); it != nodes.rend(); it++) {
if (it == nodes.rend() - 1) {
}
}
} else {
- EXPECT_THROW(nn.backwarding(label, iteration), std::runtime_error);
+ EXPECT_THROW(nn.backwarding(iteration), std::runtime_error);
}
}
}
if (loss_nodes.size()) {
- EXPECT_NO_THROW(nn.backwarding(label, 0));
+ EXPECT_NO_THROW(nn.backwarding(0));
}
/**
EXPECT_NO_THROW(
t1 = pool.requestTensor(nntrainer::TensorDim({1}), {0},
- nntrainer::TensorLifespan::ZERO_LIFESPAN, "abc"));
+ nntrainer::TensorLifespan::MAX_LIFESPAN, "abc"));
EXPECT_NE(t1, nullptr);
EXPECT_FALSE(t1->isAllocated());