Commit f6f448e1 by Tianqi Chen

[TOP] GraphExecutor (#11)

parent a86fae00
......@@ -11,6 +11,7 @@ include $(config)
export LDFLAGS = -pthread -lm
export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
CFLAGS += -Itvm/include -Itvm/dlpack/include
ifdef DMLC_CORE_PATH
CFLAGS += -I$(DMLC_CORE_PATH)/include
......@@ -51,10 +52,10 @@ else
NO_WHOLE_ARCH= --no-whole-archive
endif
all: lib/libnnvm.a lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX)
all: lib/libnnvm.a lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX) lib/libnnvm_top_runtime.$(SHARED_LIBRARY_SUFFIX)
SRC = $(wildcard src/*.cc src/c_api/*.cc src/core/*.cc src/pass/*.cc)
SRC_TOP = $(wildcard src/top/*.cc, src/top/*/*.cc)
SRC_TOP = $(wildcard src/top/*.cc, src/top/*/*.cc src/runtime/*.cc)
ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_TOP))
ALL_DEP = $(ALL_OBJ)
......@@ -76,6 +77,10 @@ lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX): lib/libnnvm.a ${TOP_OBJ}
@mkdir -p $(@D)
$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS) -Wl,${WHOLE_ARCH} lib/libnnvm.a -Wl,${NO_WHOLE_ARCH}
lib/libnnvm_top_runtime.$(SHARED_LIBRARY_SUFFIX): deploy/nnvm_runtime.cc
@mkdir -p $(@D)
$(CXX) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS)
cython:
cd python; python setup.py build_ext --inplace
......
export NNVM_ROOT=`pwd`/..
export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
ifdef DMLC_CORE_PATH
CFLAGS += -I$(DMLC_CORE_PATH)/include
else
CFLAGS += -I$(CURDIR)/../dmlc-core/include
endif
.PHONY: all clean
all: libnnvm.a
nnvm.cc:
python generate.py $@
nnvm.d: nnvm.cc
${CXX} ${CFLAGS} -M -MT nnvm.o \
-I ${NNVM_ROOT}/ -I ${NNVM_ROOT}/include \
-D__MIN__=$(MIN) $+ > nnvm.d
nnvm-all.cc: nnvm.d nnvm.cc
python ./amalgamation.py $+ $@
nnvm-all.o: nnvm-all.cc
${CXX} ${CFLAGS} -fPIC -o $@ -c $+
libnnvm.a: nnvm-all.o
ar rcs $@ $+
clean:
rm -f *.d *.o *.so *.a nnvm-all.cc nnvm.cc
import sys
import os.path, re, StringIO
blacklist = [
'Windows.h',
'mach/clock.h', 'mach/mach.h',
'malloc.h',
'glog/logging.h', 'io/azure_filesys.h', 'io/hdfs_filesys.h', 'io/s3_filesys.h',
'sys/stat.h', 'sys/types.h',
'omp.h', 'execinfo.h', 'packet/sse-inl.h'
]
def get_sources(def_file):
sources = []
files = []
visited = set()
mxnet_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir))
for line in open(def_file):
files = files + line.strip().split(' ')
for f in files:
f = f.strip()
if not f or f.endswith('.o:') or f == '\\': continue
fn = os.path.relpath(f)
if os.path.abspath(f).startswith(mxnet_path) and fn not in visited:
sources.append(fn)
visited.add(fn)
return sources
sources = get_sources(sys.argv[1])
def find_source(name, start):
candidates = []
for x in sources:
if x == name or x.endswith('/' + name): candidates.append(x)
if not candidates: return ''
if len(candidates) == 1: return candidates[0]
for x in candidates:
if x.split('/')[1] == start.split('/')[1]: return x
return ''
re1 = re.compile('<([./a-zA-Z0-9_-]*)>')
re2 = re.compile('"([./a-zA-Z0-9_-]*)"')
sysheaders = []
history = set([])
out = StringIO.StringIO()
def expand(x, pending):
if x in history and x not in ['mshadow/mshadow/expr_scalar-inl.h']: # MULTIPLE includes
return
if x in pending:
#print 'loop found: %s in ' % x, pending
return
print >>out, "//===== EXPANDING: %s =====\n" %x
for line in open(x):
if line.find('#include') < 0:
out.write(line)
continue
if line.strip().find('#include') > 0:
print line
continue
m = re1.search(line)
if not m: m = re2.search(line)
if not m:
print line + ' not found'
continue
h = m.groups()[0].strip('./')
source = find_source(h, x)
if not source:
if (h not in blacklist and
h not in sysheaders and
'mkl' not in h and
'nnpack' not in h): sysheaders.append(h)
else:
expand(source, pending + [x])
print >>out, "//===== EXPANDED: %s =====\n" %x
history.add(x)
expand(sys.argv[2], [])
f = open(sys.argv[3], 'wb')
for k in sorted(sysheaders):
print >>f, "#include <%s>" % k
print >>f, ''
print >>f, out.getvalue()
for x in sources:
if x not in history and not x.endswith('.o'):
print 'Not processed:', x
import os
import sys
FOLDERS = ["core", "pass", "c_api"]
fo = open(sys.argv[1], "w")
for folder in FOLDERS:
path = str(os.path.join("../src", folder))
flst = os.listdir(path)
for f in flst:
if f.endswith(".cc") == True:
fo.write('#include "' + str(os.path.join("src", folder, f)) + '"\n')
fo.close()
All in One Deployment File
==========================
This folder contains an all in one deployment file that contains minimum dependencies
needed to run nnvm top runtime.
\ No newline at end of file
/*!
* Copyright (c) 2017 by Contributors
* All in one runtime
* \file nnvm_runtime.cc
*/
#include "../src/core/graph.cc"
#include "../src/core/node.cc"
#include "../src/core/pass.cc"
#include "../src/core/op.cc"
#include "../src/pass/saveload_json.cc"
#include "../src/runtime/graph_executor.cc"
// Copyright (c) 2016 by Contributors
// This is an example on how we can register operator information to NNVM
// these operator information are used to support various graph building and optimizations
// see tests/python/ folder for the test-cases that uses these information.
#include <nnvm/base.h>
#include <nnvm/op.h>
#include <nnvm/op_attr_types.h>
#include <nnvm/node.h>
#include <nnvm/graph_attr_types.h>
#include <utility>
namespace myproject {
using nnvm::FListInputNames;
using nnvm::FMutateInputs;
using nnvm::FInferShape;
using nnvm::FInferType;
using nnvm::FInplaceOption;
using nnvm::Node;
using nnvm::NodePtr;
using nnvm::NodeEntry;
using nnvm::FGradient;
using nnvm::NodeAttrs;
using nnvm::TShape;
using nnvm::array_view;
// simply return the shape as same
inline bool SameShape(const NodeAttrs& attrs,
std::vector<TShape> *ishape,
std::vector<TShape> *oshape) {
if (ishape->size() == 0 || (*ishape)[0].ndim() == 0) return false;
for (TShape& pshape : *oshape) {
pshape = (*ishape)[0];
}
for (TShape& pshape : *ishape) {
pshape = (*ishape)[0];
}
return true;
}
inline std::vector<std::pair<int, int> > InplaceIn0Out0(const NodeAttrs& attrs) {
return {{0, 0}};
}
// quick helper to make node
inline NodeEntry MakeNode(const char* op_name,
std::string node_name,
std::vector<NodeEntry> inputs) {
NodePtr p = Node::Create();
p->attrs.op = nnvm::Op::Get(op_name);
p->attrs.name = std::move(node_name);
p->inputs = std::move(inputs);
return NodeEntry{p, 0, 0};
}
// simple demonstration of reshape.
NNVM_REGISTER_OP(reshape)
.describe("reshape source to target shape")
.set_num_inputs(1)
.set_attr_parser(
[](NodeAttrs* attrs) {
// parse attr parser to get target attribute
TShape target;
std::istringstream is(attrs->dict.at("target"));
CHECK(is >> target);
attrs->parsed = std::move(target);
})
.set_attr<FInferShape>(
"FInferShape", [] (const NodeAttrs& attrs,
std::vector<TShape> *ishape,
std::vector<TShape> *oshape) {
// get parsed attribute
const TShape& target = nnvm::get<TShape>(attrs.parsed);
(*oshape)[0] = target;
if ((*ishape)[0].ndim() == 0) return false;
CHECK_EQ((*ishape)[0].Size(), target.Size())
<< "Reshape op: source target shape mismatch";
return true;
})
.set_attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0);
NNVM_REGISTER_OP(cast)
.describe("cast source type to target")
.set_num_inputs(1)
.include("ElementwiseOpAttr")
.set_attr_parser(
[](NodeAttrs* attrs) {
// parse attr parser to get target attribute
int dtype;
std::istringstream is(attrs->dict.at("dtype"));
CHECK(is >> dtype);
attrs->parsed = std::move(dtype);
})
.set_attr<FInferType>(
"FInferType", [](const NodeAttrs& attrs,
std::vector<int> *itype,
std::vector<int> *otype) {
(*otype)[0] = nnvm::get<int>(attrs.parsed);
return true;
});
NNVM_REGISTER_OP(identity)
.describe("identity function")
.set_num_inputs(1)
.include("ElementwiseOpAttr")
.set_attr<FGradient>(
"FGradient", [](const NodePtr& n,
const std::vector<NodeEntry>& ograds) {
return std::vector<NodeEntry>{ograds[0]};
});
NNVM_REGISTER_OP(add)
.describe("add two data together")
.set_num_inputs(2)
.add_alias("__add_symbol__")
.include("ElementwiseOpAttr")
.set_attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
.set_attr<FGradient>(
"FGradient", [](const NodePtr& n,
const std::vector<NodeEntry>& ograds){
return std::vector<NodeEntry>{ograds[0], ograds[0]};
});
NNVM_REGISTER_OP(mul)
.describe("multiply two data together")
.set_num_inputs(2)
.include("ElementwiseOpAttr")
.set_attr<FInferShape>("FInferShape", SameShape)
.set_attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
.set_attr<FGradient>(
"FGradient", [](const NodePtr& n,
const std::vector<NodeEntry>& ograds){
return std::vector<NodeEntry>{
MakeNode("mul", n->attrs.name + "_grad_0",
{ograds[0], n->inputs[1]}),
MakeNode("mul", n->attrs.name + "_grad_1",
{ograds[0], n->inputs[0]})
};
});
NNVM_REGISTER_OP(__ewise_sum__)
.describe("elementwise sum")
.set_num_inputs(nnvm::kVarg);
NNVM_REGISTER_OP(__zero__)
.describe("set output to zero")
.set_num_inputs(0);
NNVM_REGISTER_OP(__one__)
.describe("set output to one")
.set_num_inputs(0);
NNVM_REGISTER_OP(cross_device_copy)
.describe("Copy data across device.")
.set_num_inputs(1)
.set_attr<FInferShape>("FInferShape", SameShape);
NNVM_REGISTER_OP(conv2d)
.describe("take conv of input")
.set_num_inputs(2)
.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
return std::vector<std::string>{"data", "weight"};
});
NNVM_REGISTER_OP(add)
.set_attr<std::string>("nick_name", "plus");
NNVM_REGISTER_OP(assign)
.set_num_inputs(2)
.set_num_outputs(1)
.set_attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
return std::vector<uint32_t>{0};
});
NNVM_REGISTER_OP_GROUP(ElementwiseOpAttr)
.set_attr<FInferShape>("FInferShape", SameShape);
NNVM_REGISTER_OP(exp)
.describe("take exponential")
.set_num_inputs(1)
.include("ElementwiseOpAttr")
.set_attr<FGradient>(
"FGradient", [](const NodePtr& n,
const std::vector<NodeEntry>& ograds) {
return std::vector<NodeEntry>{
MakeNode("mul", n->attrs.name + "_grad",
{ograds[0], NodeEntry{n, 0, 0}})
};
});
} // namespace myproject
......@@ -81,8 +81,6 @@ struct NodeAttrs {
const Op *op{nullptr};
/*! \brief name of the node */
std::string name;
/*! \brief Vector representation of positional attributes */
std::vector<double> scalars;
/*! \brief The dictionary representation of attributes */
std::unordered_map<std::string, std::string> dict;
/*!
......
......@@ -195,7 +195,7 @@ class Tuple {
* \return the ostream
*/
friend std::ostream &operator<<(std::ostream &os, const Tuple<ValueType> &t) {
os << '(';
os << '[';
const ValueType* begin = t.begin();
const ValueType* end = t.end();
for (const ValueType* it = begin; it != end; ++it) {
......@@ -204,7 +204,7 @@ class Tuple {
}
// python style tuple
if (t.ndim() == 1) os << ',';
os << ')';
os << ']';
return os;
}
/*!
......@@ -235,7 +235,7 @@ class Tuple {
while (isspace(is.peek())) {
is.get();
}
if (is.peek() == ')') {
if (is.peek() == ')' || is.peek() == ']') {
is.get();
return is;
}
......
Project Structure
=================
The following components are operator invariant.
- c_api: NNVM C API
- core: NNVM core data structure
- pass: NNVM pass
The following components are generic graph compiler for NNVM-TOP
- top: NNVM-TOP core operator defs
- tvm: NNVM-TOP to TVM compiler toolchain
- runtime: NNVM-TOP runtime
/*!
* Copyright (c) 2017 by Contributors
*
* Runtime module for graph deployment.
*
* \file graph_executor.h
*/
#ifndef NNVM_RUNTIME_GRAPH_EXECUTOR_H_
#define NNVM_RUNTIME_GRAPH_EXECUTOR_H_
#include <dmlc/io.h>
#include <tvm/runtime/packed_func.h>
#include <tvm/runtime/module.h>
#include <nnvm/graph.h>
#include <nnvm/graph_attr_types.h>
#include <nnvm/tuple.h>
#include <nnvm/pass.h>
#include <vector>
#include <string>
namespace nnvm {
namespace runtime {
/*! \brief Magic number for NDArray file */
constexpr uint64_t kTVMNDArrayMagic = 0xDD5E40F096B4A13F;
/*! \brief Magic number for NDArray list file */
constexpr uint64_t kTVMNDArrayListMagic = 0xF7E58D4F05049CB7;
/*! \brief DLPack compatible data types */
using DLTypeVector = std::vector<DLDataType>;
/*! \brief operator attributes about tvm op */
struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
std::string func_name;
uint32_t num_inputs;
uint32_t num_outputs;
bool flatten_data;
DMLC_DECLARE_PARAMETER(TVMOpParam) {
DMLC_DECLARE_FIELD(func_name);
DMLC_DECLARE_FIELD(num_inputs).set_default(1);
DMLC_DECLARE_FIELD(num_outputs).set_default(1);
DMLC_DECLARE_FIELD(flatten_data).set_default(false);
}
};
/*!
* \brief TVM Graph Executor.
* This is a minimum graph executor, embedded in TVM runtime
* without any framework dependency.
*
* This runtime can be acccesibly in various language via
* TVM runtime PackedFunc API.
*/
class GraphExecutor : public ::tvm::runtime::ModuleNode {
public:
/*!
* \return The type key of the executor.
*/
const char* type_key() const final {
return "GraphExecutor";
}
/*!
* \brief Get member function to front-end
* \param name The name of the function.
* \param sptr_to_self The pointer to the module node.
* \return The corresponding member function.
*/
tvm::runtime::PackedFunc GetFunction(
const std::string& name,
const std::shared_ptr<ModuleNode>& sptr_to_self) final;
/*! \brief destructor */
~GraphExecutor();
/*!
* \brief Initialize the graph executor with graph and context.
* \param graph The execution graph.
* \param module The module containing the compiled functions.
* \param ctx The context where the graph should sit on
*/
void Init(Graph graph,
tvm::runtime::Module module,
TVMContext ctx);
/*!
* \brief Get the input index given the name of input.
* \param name The name of the input.
* \return The index of input.
*/
int GetInputIndex(const std::string& name);
/*!
* \brief set index-th input to the graph.
* \param index The input index.
* \param data The input data.
*/
void SetInput(int index, DLTensor* data);
/*!
* \brief Copy index-th output to data_out.
* \param index The output index.
* \param data_out the output data.
*/
void GetOutput(int index, DLTensor* data_out);
/*!
* \brief Load parameters from binary stream
* \param strm The input stream.
*/
void LoadParams(dmlc::Stream* strm);
/*!
* \brief Load parameters from parameter blob.
* \param param_blob A binary blob of parameter.
*/
void LoadParams(const std::string& param_blob);
/*!
* \brief Execute the graph, update output.
*/
void Run();
private:
/*! \brief Setup the temporal storage */
void SetupStorage();
/*! \brief Setup the executors */
void SetupOpExecs();
/*!
* \brief Create a executtion function given input.
* \param attrs The node attributes
* \param args The arguments to the functor, including inputs and outputs.
* \param num_inputs Number of inputs
* \return The created executor.
*/
std::function<void()> CreateTVMOp(const NodeAttrs& attrs,
const std::vector<DLTensor>& args,
size_t num_inputs);
/*! \brief The graph */
Graph graph_;
/*! \brief The code module */
tvm::runtime::Module module_;
/*! \brief execution context */
TVMContext ctx_;
/*! \brief common storage pool */
std::vector<DLTensor*> storage_pool_;
/*! \brief data shape of each node entry */
std::vector<TShape> data_shape_;
/*! \brief data entry of each node */
std::vector<DLTensor> data_entry_;
/*! \brief operator on each node */
std::vector<std::function<void()> > op_execs_;
};
} // namespace runtime
} // namespace nnvm
#endif // NNVM_RUNTIME_GRAPH_EXECUTOR_H_
Core Operator List
==================
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment