cavis/libnd4j/include/graph/impl/GraphExecutioner.cpp

906 lines
33 KiB
C++

/*******************************************************************************
* Copyright (c) 2015-2018 Skymind, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author raver119@gmail.com
//
#include <graph/generated/node_generated.h>
#include <graph/generated/graph_generated.h>
#include <graph/generated/result_generated.h>
//#include <protobuf/core/framework/graph.pb.h>
#include <graph/Variable.h>
#include <graph/VariableSpace.h>
#include <memory/MemoryRegistrator.h>
#include <graph/Node.h>
#include <graph/Scope.h>
#include <graph/GraphExecutioner.h>
#include <graph/TimeHolder.h>
#include <loops/scalar.h>
#include <loops/pairwise_transform.h>
#include <loops/transform_same.h>
#include <ops/declarable/DeclarableOp.h>
//#include <google/protobuf/text_format.h>
//#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <fcntl.h>
#include <chrono>
#include <ctime>
#include <graph/execution/LogicExecutor.h>
#include <array/DataTypeUtils.h>
#include <helpers/BitwiseUtils.h>
#include <graph/generated/array_generated.h>
#include <helpers/ShapeUtils.h>
#include <graph/Status.h>
#include <deque>
#include <graph/ResultWrapper.h>
#include <graph/ExecutionResult.h>
#include <exceptions/graph_execution_exception.h>
#include <exceptions/no_results_exception.h>
#include <graph/FlatUtils.h>
namespace sd{
namespace graph {
/**
* This method executes given Node (as in Op within Node)
*
* Basically it just does DeclarableOp::execute(Block), and ops to their job. However, there are some additional functionality.
*
* @param graph - Graph instance pointer
* @param node - Node instance pointer, which will be executed
* @param variableSpace - VariableSpace instance pointer - varspace specific to current Thread/Session
* @return
*/
Nd4jStatus GraphExecutioner::executeFlatNode(Graph *graph, Node *node, VariableSpace *variableSpace) {
OpType opType = node->opType();
int opNum = node->opNum();
// std::string opName = *(node->getCustomOp()->getOpName());
if (opType == OpType_BOOLEAN) {
nd4j_debug("Executing boolean graph node_%i", node->id());
} else if (opType == OpType_LOGIC) {
nd4j_debug("Executing logic graph node_%i", node->id());
} else if (opType == OpType_GRAPH) {
nd4j_debug("Executing embedded graph node_%i", node->id());
} else if (opType != OpType_CUSTOM) {
nd4j_debug("Executing node_%i{%i}\n", node->id(), opNum);
} else {
nd4j_debug("Executing node_%i{%s}\n", node->id(), node->getCustomOp()->getOpName()->c_str());
}
Context context(node->getContextPrototype(), variableSpace);
if (sd::Environment::getInstance()->isDebugAndVerbose()) {
//nd4j_debug("Input variables: %i\n", node->input()->size());
printf(" Inputs: {");
for (int e = 0; e < node->input()->size(); e++) {
printf("[%i:%i]", node->input()->at(e).first, node->input()->at(e).second);
if (e < node->input()->size() - 1)
printf(", ");
}
printf("}\n");
fflush(stdout);
}
if (node->id() == 13)
nd4j_debug("","");
// if true - this is special case: Graph-in-Graph.
if (node->hasGraphEmbedded()) {
auto embedded = node->getGraph();
/**
* basically, we should do following things here:
* 1) fill embedded graph with input variables from this graph, if anything should be filled in
* 2) invoke embedded graph
* 3) announce its results as corresponding output variables in current VariableSpace
*/
// enforcing IMPLICIT mode. or not... should we try to be smarter then user?
//embedded->getExecutorConfiguration()->_outputMode = OutputMode_IMPLICIT;
if (node->input()->size() != embedded->numberOfPlaceholders()) {
nd4j_debug("Placeholders amount mismatch: %i expected, and %i available\n",node->input()->size(), embedded->numberOfPlaceholders());
return ND4J_STATUS_BAD_INPUT;
}
// we need to propagate required variables to the embedded graph
ResultSet deletables;
int cnt = 0;
for (Variable* v: *embedded->getPlaceholders()) {
if (v->getName() != nullptr && v->getName()->size() > 0) {
// trying symbolic lookup first
if (variableSpace->hasVariable(v->getName())) {
// symbolic feeder
auto array = variableSpace->getVariable(v->getName())->getNDArray();
auto vr = new NDArray(array->dup());
// deletables.push_back(vr);
v->setNDArray(vr);
} else {
nd4j_debug("Can't find variable [%s] in parent graph...", v->getName()->c_str());
return ND4J_STATUS_BAD_INPUT;
//throw "Can't find desired variable";
}
} else {
// if we're not using symbolic lookup - we'll use sequential approach then
auto p = node->input()->at(cnt);
auto array = variableSpace->getVariable(p)->getNDArray();
auto vr = new NDArray(array->dup());
//deletables.push_back(vr);
v->setNDArray(vr);
}
cnt++;
}
// executing embedded graph as independent one
Nd4jStatus status = GraphExecutioner::execute(embedded);
if (status != ND4J_STATUS_OK)
return status;
// now we should migrate its results to this node, as its own outputs
cnt = 0;
auto outputs = embedded->fetchOutputs();
for (auto v: *outputs){
NDArray *array = v->getNDArray();
v->setNDArray(nullptr);
std::pair<int,int> pair(node->id(), cnt++);
auto var = variableSpace->getVariable(pair);
//nd4j_printf("HasArray: [%i]; Removable: [%i]\n", var->hasNDArray(), var->isRemovable());
var->setNDArray(array);
var->markRemovable(true);
}
deletables.size();
delete outputs;
nd4j_debug("Embedded graph execution finished. %i variable(s) migrated\n", cnt);
} else if (node->hasCustomOp()) {
// now, if we have something to execute - lets just execute it.
auto status = node->getCustomOp()->execute(&context);
if (status != ND4J_STATUS_OK)
return status;
// propagate variables
if (node->hasExternalOutputs()) {
for (auto v: *node->output()) {
if (variableSpace->hasExternalVariable(v.first)) {
variableSpace->getVariable(v.first)->getNDArray()->assign(variableSpace->getVariable(node->id())->getNDArray());
}
}
}
return status;
}
return ND4J_STATUS_OK;
}
/**
* This method executes given Graph instance, and returns error code.
*
* @param graph
* @return one of error codes defined in pointercast.h
*/
Nd4jStatus GraphExecutioner::execute(Graph *graph, VariableSpace* variableSpace) {
auto __variableSpace = variableSpace == nullptr ? graph->getVariableSpace() : variableSpace;
bool tempFlow = false;
if (__variableSpace->flowPath() == nullptr) {
tempFlow = true;
__variableSpace->setFlowPath(new FlowPath());
}
auto flowPath = __variableSpace->flowPath();
Nd4jLong tb0 = Environment::getInstance()->isProfiling() ? GraphProfile::currentTime() : 0L;
graph->buildGraph();
auto footprintForward = sd::memory::MemoryRegistrator::getInstance()->getGraphMemoryFootprint(graph->hashCode());
if (footprintForward > 0) {
if (__variableSpace->launchContext()->getWorkspace() != nullptr) {
// this method will work only if current workspace size is smaller then proposed value
nd4j_debug("Setting workspace to %lld bytes\n", footprintForward);
__variableSpace->launchContext()->getWorkspace()->expandTo(footprintForward);
}
}
// optionally saving graph build time
if (Environment::getInstance()->isProfiling())
flowPath->profile()->setBuildTime(GraphProfile::relativeTime(tb0));
Nd4jLong timeStart = Environment::getInstance()->isProfiling() ? GraphProfile::currentTime() : 0L;
bool pe = graph->getExecutorConfiguration()->_executionMode == ExecutionMode_AUTO;
// basically if at some point code diverges, code branch might be _DISABLED_, and all nodes within that branch will be disabled as well
std::deque<Nd4jLong> frames;
bool inFrame = false;
bool leftFrame = false;
auto nodeTime = GraphProfile::currentTime();
int lastId = -10000000;
Nd4jLong exec_counter = 0;
// we loop through op layers here
for (int l = 0; l < (int) graph->getOnion()->size(); l++) {
int layerSize = graph->getOnion()->count(l) == 1 ? graph->getOnion()->at(l)->size() : 0;
int n = 0;
// this omp block will probably never be the case
for (; n < layerSize; n++) {
if (++exec_counter > 10000) {
l = graph->getOnion()->size();
return Status::THROW("Early termination hit");
}
Node* node = graph->getOnion()->at(l)->at(n);
if (Environment::getInstance()->isProfiling())
flowPath->profile()->nodeById(node->id(), node->name()->c_str());
if (lastId != node->id() && Environment::getInstance()->isProfiling()) {
if (lastId != -10000000)
flowPath->profile()->nodeById(lastId)->setTotalTime(GraphProfile::relativeTime(nodeTime));
lastId = node->id();
nodeTime = GraphProfile::currentTime();
}
nd4j_debug("Step: %lld; Node: %i <%s>\n", exec_counter, node->id(), node->name()->c_str());
// on first non-Exit node after loop we can rewind (if planned)
if (!(node->opType() == OpType_LOGIC && node->opNum() == sd::logic::Exit)) {
// VALIDATED
// if we're out of frame - let's remove it from queue
if (leftFrame) {
auto frame_id = frames.back();
frames.pop_back();
flowPath->markFrameActive(frame_id, false);
flowPath->forgetFrame(frame_id);
leftFrame = false;
}
// TODO: move inactivity check right here
bool shouldSkip = false;
if (node->opType() == OpType_LOGIC && node->opNum() == sd::logic::Merge) {
// Merge node has own checkout logic
auto inputId0 = node->input()->at(0);
auto inputId1 = node->input()->at(1);
// Merge node can be skipped only both inputs are inactive
if (!flowPath->isNodeActive(inputId0.first) && !flowPath->isNodeActive(inputId1.first))
shouldSkip = true;
} else {
// let's check for input nodes, if they are disabled or contain divergents
for (int e = 0; e < node->input()->size(); e++) {
auto inputId = node->input()->at(e);
// not a node. skipping checks
if (graph->getMapped()->count(inputId.first) == 0)
continue;
/**
* We can skip current node, in two cases:
* 1) If previous node was disabled
* 2) If previous node was divergent node (i.e. IF op) and code went other way
*/
Node *prevNode = graph->getMapped()->at(inputId.first);
if (!flowPath->isNodeActive(inputId.first)) {
shouldSkip = true;
flowPath->markNodeActive(node->id(), false);
nd4j_debug("Skipping Node_%i due to inactive input [%i]\n", node->id(), inputId.first);
break;
} else if (prevNode->isDivergencePoint()) { // literally checking for switch here
if (flowPath->branch(inputId.first) != inputId.second) {
shouldSkip = true;
flowPath->markNodeActive(node->id(), false);
nd4j_debug("Skipping Node_%i due to divergent branch [%i]\n", node->id(),
inputId.first);
break;
}
}
}
}
if (shouldSkip)
continue;
}
// we're propagating frameId here (but only if wasn't set earlier)
if (frames.size() > 0 && node->getFrameId() < 0)
node->setFrameId(frames.back());
flowPath->markNodeActive(node->id(), true);
if (node->opType() == OpType_LOGIC && node->opNum() == sd::logic::Enter) {
// Enter operation
// VALIDATED
// we expect this node to have frameId set
auto frame_id = node->getFrameId();
// new frame starts here
if (frames.size() == 0 || (frames.size() > 0 && frames.back() != frame_id)) {
flowPath->registerFrame(frame_id);
frames.emplace_back(frame_id);
inFrame = true;
}
auto status = LogicExecutor::processNode(graph, node);
if (status != Status::OK())
return status;
} else if (node->opType() == OpType_LOGIC && node->opNum() == sd::logic::NextIteration) {
/**
* NextIteration is special case: after successful execution of this op - we're changing execution position
*/
// VALIDATED
auto inputId = node->input()->at(0);
auto status = LogicExecutor::processNode(graph, node);
if (status != Status::OK())
return status;
auto frame_id = frames.back();
flowPath->markNodeActive(node->id(), true);
flowPath->markExecuted(node->id(), true);
if (!flowPath->isRewindPlanned(frame_id)) {
auto nextLayer = node->getRewindLayer();
nd4j_debug("Node_%i planned rewind to Node_%i at [%i:%i]\n", node->id(), node->getRewindNode(), nextLayer.first, nextLayer.second);
flowPath->planRewind(frame_id, true);
flowPath->setRewindPositionOnce(frame_id, nextLayer.first - 1);
continue;
}
} else if (node->opType() == OpType_LOGIC && node->opNum() == sd::logic::Exit) {
// Exit node is another special case: it can rewind executioner to specific point in graph
// VALIDATED
auto frame_id = frames.back();
// if this loop frame wasn't activated - just skip it
if (!flowPath->isFrameActive(frame_id)) {
flowPath->markNodeActive(node->id(), false);
leftFrame = true;
continue;
}
if (flowPath->isRewindPlanned(frame_id)) {
// just break loop here
l = flowPath->getRewindPosition(frame_id);
flowPath->setRewindPosition(frame_id, -1);
flowPath->planRewind(frame_id, false);
break;
} else {
// execute Exit node otherwise
auto status = LogicExecutor::processNode(graph, node);
if (status != Status::OK())
return status;
leftFrame = true;
}
} else if (node->opType() == OpType_LOGIC) {
/**
* If this LOGIC op, we'll use another execution model here
*/
auto status = LogicExecutor::processNode(graph, node);
if (status != Status::OK())
return status;
} else {
auto timeStart = std::chrono::system_clock::now();
// actual node execution happens right here
Nd4jStatus status = executeFlatNode(graph, node, __variableSpace);
auto timeEnd = std::chrono::system_clock::now();
auto outerTime = std::chrono::duration_cast<std::chrono::nanoseconds>(timeEnd - timeStart).count();
flowPath->setOuterTime(node->id(), outerTime);
if (status != ND4J_STATUS_OK)
return status;
// here we should handle divergent ops, and disable nodes accordingly
if (node->isDivergencePoint()) {
auto activeBranch = flowPath->branch(node->id());
nd4j_debug("Active branch at node [%i]: %i\n", node->id(), activeBranch);
// now we skip all branches except of this active one
}
if (sd::Environment::getInstance()->isDebugAndVerbose()) {
if (__variableSpace->getVariable(node->id())->hasNDArray()) {
auto array = __variableSpace->getVariable(node->id())->getNDArray();
auto shape = ShapeUtils::shapeAsString(array);
auto values = array->asIndexedString(16);
auto type = DataTypeUtils::asString(array->dataType());
nd4j_debug("node_%i finished. result shape: %s; data type: %s; first values: %s\n", node->id(), shape.c_str(), type.c_str(), values.c_str());
} else if (__variableSpace->getVariable(node->id())->hasNDArrayList()) {
auto list = __variableSpace->getVariable(node->id())->hasNDArrayList() ? __variableSpace->getVariable(node->id())->getNDArrayList() : nullptr;
nd4j_debug("node_% is ListOp, skipping evaluation", node->id());
} else {
nd4j_debug("node_% is Unknown: has no NDArray or NDArrayList", node->id());
}
}
}
// if node was executed - tag it as active
flowPath->markExecuted(node->id(), true);
}
}
// optionally saving execution time
if (Environment::getInstance()->isProfiling()) {
flowPath->profile()->nodeById(lastId)->setTotalTime(GraphProfile::relativeTime(nodeTime));
flowPath->profile()->setExecutionTime(GraphProfile::relativeTime(timeStart));
//flowPath->profile().printOut();
}
// saving memory footprint for current run
if (__variableSpace->launchContext()->getWorkspace() != nullptr) {
auto m = __variableSpace->launchContext()->getWorkspace()->getAllocatedSize();
auto h = graph->hashCode();
sd::memory::MemoryRegistrator::getInstance()->setGraphMemoryFootprintIfGreater(h, m);
}
if (tempFlow) {
delete flowPath;
__variableSpace->setFlowPath(nullptr);
}
return Status::OK();
}
/**
* This method is provided for IPC:
* 1) it accepts pointer to FlatBuffers buffer
* 2) restores Graph from it
* 3) Executes this Graph
* 4) Packs execution results into FlatBuffers (FlatResults instance)
* 5) Returns pointer to FlatBuffer results buffer
*
*/
sd::graph::ResultWrapper* GraphExecutioner::executeFlatBuffer(Nd4jPointer pointer) {
uint8_t *buffer = reinterpret_cast<uint8_t *>(pointer);
// nd4j_debug("Trying to restore graph\n", 0);
auto restoredGraph = GetFlatGraph(buffer);
// nd4j_debug("Graph restored\n", 0);
// converting FlatGraph to internal representation
auto nativeGraph = new Graph(restoredGraph);
if (Environment::getInstance()->isDebugAndVerbose()) {
nativeGraph->printOut();
}
FlowPath flowPath;
nativeGraph->getVariableSpace()->setFlowPath(&flowPath);
// nd4j_debug("Going to execute graph\n", 0);
// executing internal representation
auto status = GraphExecutioner::execute(nativeGraph);
if (status != ND4J_STATUS_OK) {
nd4j_printf("Graph execution failed with status: [%i]\n", status)
return nullptr;
}
// nd4j_debug("Building output...\n", 0);
flatbuffers::FlatBufferBuilder builder(1024);
// fetching time reports
std::vector<flatbuffers::Offset<FlatTiming>> timings_vector;
for (int e = 0; e < (int) nativeGraph->getAllNodes()->size(); e++) {
Node *node = nativeGraph->getAllNodes()->at(e);
if (node->getContextPrototype() == nullptr)
continue;
auto pair = CreateLongPair(builder, flowPath.outerTime(node->id()), flowPath.innerTime(node->id()));
if (node->getName() != nullptr) {
auto name = builder.CreateString(node->getName()->c_str());
auto fr = CreateFlatTiming(builder, node->id(), name, pair);
timings_vector.push_back(fr);
} else {
auto fr = CreateFlatTiming(builder, node->id(), 0, pair);
timings_vector.push_back(fr);
}
}
// now, we'll prepare output, depending on given outputmode
auto outputs = nativeGraph->fetchOutputs();
auto size = static_cast<int>(outputs->size());
int arrays = 0;
std::vector<flatbuffers::Offset<FlatVariable>> variables_vector;
for (int e = 0; e < size; e++) {
auto var = outputs->at(e);
// FIXME: we want to export multi-output nodes as well
// FIXME: we want to export NDArrayList and skip nodes without outputs
if (!var->hasNDArray())
continue;
auto array = var->getNDArray();
auto fArray = FlatUtils::toFlatArray(builder, *array);
auto fName = builder.CreateString(*(var->getName()));
auto id = CreateIntPair(builder, var->id(), var->index());
auto fv = CreateFlatVariable(builder, id, fName, static_cast<sd::graph::DType>(array->dataType()), 0, fArray);
variables_vector.push_back(fv);
arrays++;
}
nd4j_debug("Returning %i variables back\n", arrays);
auto varTimings = builder.CreateVector(timings_vector);
auto varVectors = builder.CreateVector(variables_vector);
auto result = CreateFlatResult(builder, restoredGraph->id(), varVectors, varTimings);
builder.Finish(result);
// we might want to keep this graph for future
delete outputs;
delete nativeGraph;
char* res = new char[builder.GetSize()];
memcpy(res, builder.GetBufferPointer(), builder.GetSize());
nd4j_debug("Buffer size: %lld\n", static_cast<Nd4jLong>(builder.GetSize()));
return new ResultWrapper(builder.GetSize(), reinterpret_cast<Nd4jPointer>(res));
}
Graph* GraphExecutioner::importFromTensorFlow(const char *fileName) {
/*
if (fileName == nullptr)
return nullptr;
int fd = open(fileName, O_RDONLY);
if (fd < 0) {
nd4j_printf("File not found: [%s]\n", fileName);
return nullptr;
}
nd4j_verbose("Trying to load TF GraphDef from file [%s]\n", fileName);
tensorflow::GraphDef graphDef;
bool res = graphDef.ParseFromFileDescriptor(fd);
// trying to read graph as text
if(!res) {
close(fd);
fd = open(fileName, O_RDONLY);
google::protobuf::io::FileInputStream fileInput(fd);
fileInput.SetCloseOnDelete(true);
if (!google::protobuf::TextFormat::Parse(&fileInput, &graphDef)) {
nd4j_printf("Failed to read file\n","");
} else {
res = true;
}
}
close(fd);
if (!res)
return nullptr;
auto graph = new Graph();
auto variableSpace = graph->getVariableSpace();
std::map<const std::string, int> variablesMap;
int variablesCounter = 0;
int nodesCounter = 0;
nd4j_verbose("Number of nodes in graphDef: %i\n", graphDef.node_size());
for (int n = 0; n < graphDef.node_size(); n++) {
auto node = graphDef.node(n);
// if that's external variable - we put it to variable space
if (strcmp(TF_VAR, node.op().c_str()) == 0 || strcmp(TF_CONST, node.op().c_str()) == 0 || strcmp(TF_INPUT, node.op().c_str()) == 0) {
nd4j_printf("Variable found: %s\n", node.name().c_str());
auto variable = new Variable();
variable->setName(new std::string(node.name().c_str()));
variable->setId(--variablesCounter);
variableSpace->putVariable(variable->id(), variable);
std::pair<const std::string, int> pair(node.name(), variable->id());
variablesMap.insert(pair);
// TODO: we might want to have something like that.
// it basically just gives input validation option, since settles expectations for input
if (strcmp(TF_INPUT, node.op().c_str()) == 0)
continue;
// checking shape, not applicable to input, since it can vary
if (node.attr().count("shape")) {
auto attr = node.attr().at("shape");
int dims = attr.shape().dim_size();
if (dims > 0) {
std::vector<int> __shape;
// we don't have rank1 arrays. vector is 2d.
if (dims == 1)
__shape.push_back(1);
// roll through dimensions
for (auto s: attr.shape().dim()) {
__shape.push_back((int) s.size()) ;
}
variable->setNDArray(new NDArray('c', __shape));
nd4j_printf("Shape found: %i dims;\n", dims);
variable->getNDArray()->printShapeInfo();
}
}
// checking tensor attached
if (node.attr().count("value")) {
auto attr = node.attr().at("value");
// int
if (attr.tensor().dtype() == ::tensorflow::DataType::DT_INT32) {
nd4j_verbose("Int size: %i\n", attr.tensor().int_val_size());
Nd4jLong __length = 0;
nd4j_verbose("Tensor has shape: %i\n", attr.tensor().has_tensor_shape());
if (attr.tensor().has_tensor_shape()) {
auto shape = attr.tensor().tensor_shape();
int dims = shape.dim_size();
if (dims > 0) {
std::vector<int> __shape;
// we don't have rank1 arrays. vector is 2d.
if (dims == 1)
__shape.push_back(1);
// roll through dimensions
for (auto s: shape.dim()) {
__shape.push_back((int) s.size());
}
variable->setNDArray(new NDArray('c', __shape));
__length = variable->getNDArray()->lengthOf();
nd4j_printf("Tensor shape found: %i dims;\n", dims);
variable->getNDArray()->printShapeInfo();
}
}
// it can be valueOf array
if (attr.tensor().int_val_size() == 1 && __length > 0) {
variable->getNDArray()->assign((T) attr.tensor().int_val(0));
}
}
}
} else {
nd4j_verbose("Node id: [%i]; name: [%s]; opName: [%s]\n", n + 1, node.name().c_str(),
node.op().c_str());
sd::ops::DeclarableOp *op = sd::ops::OpRegistrator::getInstance()->getOperationFloat(node.op().c_str());
if (op == nullptr) {
nd4j_verbose("Op wasn't found: %s\n", node.op().c_str());
return nullptr;
}
auto jNode = new Node();
jNode->setName(node.name());
jNode->setId(++nodesCounter);
jNode->setCustomOp(op);
jNode->setBlock(new Block(jNode->id(), variableSpace));
std::pair<const std::string, int> pair(node.name(), jNode->id());
variablesMap.insert(pair);
// multi-output nodes require special treatment
for (int e = 0; e < op->getOpDescriptor()->getNumberOfOutputs(); e++) {
std::string deepName(node.name());
deepName += ":" + std::to_string(e);
auto deepVar = new Variable();
deepVar->setName(&deepName);
if (e > 0)
deepVar->setId(--variablesCounter);
else
deepVar->setId(jNode->id());
std::pair<const std::string, int> pair(deepName, deepVar->id());
variablesMap.insert(pair);
variableSpace->putVariable(deepVar->id(), deepVar);
std::pair<int, int> nodepair(jNode->id(), e);
variableSpace->putVariable(nodepair, deepVar);
}
printf(" Inputs: [");
for (int i = 0; i < node.input_size(); i++) {
nd4j_printf("Trying input: %s\n", node.input(i).c_str());
// if this fails - we're probably on partial input :)
if (!variablesMap.count(node.input(i)))
return nullptr;
printf("%s (%i)", node.input(i).c_str(), variablesMap.at(node.input(i)));
jNode->pickInput(variablesMap.at(node.input(i)));
jNode->getBlock()->pickInput(variablesMap.at(node.input(i)));
if (i < node.input_size() + 1)
printf(", ");
}
printf("]\n");
graph->addNode(jNode);
}
}
return graph;
*/
return nullptr;
}
/**
* This function returns file size for the given file name, or -1 if something went wrong
*/
long getFileSize(const char * filename) {
struct stat stat_buf;
int rc = stat(filename, &stat_buf);
return rc == 0 ? stat_buf.st_size : -1;
}
/**
* Helper function, that loads given filename into uint8_t array
*
*/
uint8_t* readFlatBuffers(const char * filename) {
long fileLen = getFileSize(filename);
if (fileLen < 0) {
nd4j_printf("File [%s] wasn't found. Please check path and permissions\n", filename);
throw std::runtime_error("File not found");
}
nd4j_debug("File length: %i\n", fileLen);
uint8_t * data = new uint8_t[fileLen];
FILE *in = fopen(filename, "rb");
int cnt = 0;
int b = 0;
while (cnt < fileLen) {
b = fread(data + cnt, 1, 1, in);
cnt += b;
}
fclose(in);
return data;
}
flatbuffers::Offset<FlatResult> GraphExecutioner::execute(Graph *graph, flatbuffers::FlatBufferBuilder &builder, const FlatInferenceRequest* request) {
ExecutionResult result;
auto varSpace = graph->getVariableSpace();
if (request != nullptr && request->variables() != nullptr) {
auto vars = request->variables();
for (int e = 0; e < vars->size(); e++) {
auto fv = vars->Get(e);
auto v = new Variable(fv);
varSpace->replaceVariable(v);
}
}
if (Environment::getInstance()->isDebugAndVerbose())
graph->printOut();
auto status = GraphExecutioner::execute(graph);
if (status != sd::Status::OK())
throw graph_execution_exception(request->id());
auto outputs = graph->fetchOutputs();
if (outputs->size() == 0)
throw no_results_exception(request->id());
for (auto v: *outputs) {
result.emplace_back(v);
}
auto t = result.asFlatResult(builder);
delete outputs;
return t;
}
/**
* This method reads given FlatBuffers file, and returns Graph instance
*
* PLEASE NOTE: This method is mostly suited for tests and debugging/profiling
*/
Graph* GraphExecutioner::importFromFlatBuffers(const char *filename) {
auto data = readFlatBuffers(filename);
auto restoredGraph = importFromFlatPointer(reinterpret_cast<Nd4jPointer>(data));
delete[] data;
return restoredGraph;
}
Graph *GraphExecutioner::importFromFlatPointer(Nd4jPointer ptr) {
auto fg = GetFlatGraph(reinterpret_cast<uint8_t *>(ptr));
auto restoredGraph = new Graph(fg);
return restoredGraph;
}
}
}