diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a6b85e43c0d..76da288eba9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ else(MSVC) include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-std=c++11" SUPPORT_CXX11) if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + message("Build in Debug mode") set(CMAKE_C_FLAGS "-O0 -g -Wall -fPIC ${CMAKE_C_FLAGS} -rdynamic") set(CMAKE_CXX_FLAGS "-O0 -g -Wall -fPIC -std=c++11 ${CMAKE_CXX_FLAGS} -rdynamic") else() diff --git a/Jenkinsfile b/Jenkinsfile index dc3a56234509..4765538a3806 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -214,7 +214,7 @@ stage('Build') { } stage('Unit Test') { - parallel 'python2/3: GPU': { + parallel 'python3: GPU': { node('GPU') { ws('workspace/tvm/ut-python-gpu') { init_git() @@ -226,7 +226,7 @@ stage('Unit Test') { } } }, - 'python2/3: i386': { + 'python3: i386': { node('CPU') { ws('workspace/tvm/ut-python-i386') { init_git() diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py index c0470ade60f9..ce6f0602a572 100644 --- a/python/tvm/__init__.py +++ b/python/tvm/__init__.py @@ -18,6 +18,8 @@ """TVM: Low level DSL/IR stack for tensor computation.""" from __future__ import absolute_import as _abs +from . import _pyversion + from . import tensor from . import arith from . import expr diff --git a/python/tvm/_pyversion.py b/python/tvm/_pyversion.py new file mode 100644 index 000000000000..a46b22028387 --- /dev/null +++ b/python/tvm/_pyversion.py @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +"""Python2 version check +""" +import sys + +if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 5): + PY3STATEMENT = """TVM project proudly dropped support of Python2. + The minimal Python requirement is Python 3.5 + """ + raise Exception(PY3STATEMENT) diff --git a/python/tvm/relay/backend/graph_runtime_codegen.py b/python/tvm/relay/backend/graph_runtime_codegen.py index cc3f14ba3951..ea1846b93beb 100644 --- a/python/tvm/relay/backend/graph_runtime_codegen.py +++ b/python/tvm/relay/backend/graph_runtime_codegen.py @@ -21,7 +21,7 @@ First we define a compiler from a single Relay expression to the graph langauge. We require the expression to be a function. -The function's parameters correpond to the placeholder/inputs +The function's parameters correspond to the placeholder/inputs and model parameters found in the computation graph representation. The body of the function represents the computation graph. @@ -31,387 +31,44 @@ To connect to the graph runtime, we use a printer that converts our graph format into TVM's JSON format. The resulting string can be loaded by -contrib.graph_runtime or any other TVM runtime comptatible system. +contrib.graph_runtime or any other TVM runtime compatible systems. """ - from __future__ import absolute_import -import json -from collections import defaultdict, OrderedDict -import attr -from . import _backend -from . import compile_engine -from ..op import Op -from ..expr import Function, GlobalVar -from ..expr_functor import ExprFunctor -from ..ty import TupleType, TensorType -from ... import target as _target - - -@attr.s -class NodeRef(object): - """A reference to a node, used for constructing the graph.""" - ident = attr.ib() - index = attr.ib(default=0) - version = attr.ib(default=0) - - def to_json(self): - return [self.ident, self.index, self.version] - - -@attr.s -class Node(object): - """The base class for nodes in the TVM runtime system graph input.""" - name = attr.ib() - attrs = attr.ib() - - def to_json(self): - raise Exception("Abstract method, please implement me.") - - -@attr.s -class InputNode(Node): - """An input node in the TVM runtime system graph input.""" - name = attr.ib() - attrs = attr.ib() - - def to_json(self): - return { - "op": "null", - "name": self.name, - "inputs": [] - } +from tvm.ndarray import empty +from tvm._ffi.function import _init_api -@attr.s -class OpNode(Node): - """An operator node in the TVM runtime system"s graph input.""" - op_name = attr.ib() - inputs = attr.ib() - op_attrs = attr.ib() - num_outputs = attr.ib(default=1) +from tvm.relay import build_module +from tvm import target as _target - def to_json(self): - attrs = dict.copy(self.op_attrs) - # Extend ops with extra info. - attrs["func_name"] = self.op_name - attrs["flatten_data"] = "0" - attrs["num_inputs"] = str(len(self.inputs)) - attrs["num_outputs"] = str(self.num_outputs) +_init_api("tvm.relay.build_module") - return { - "op": "tvm_op", - "name": self.name, - "attrs": attrs, - "inputs": self.inputs - } - - -def shape_to_json(shape): - """Convert symbolic shape to json compatible forma.""" - return [sh.value for sh in shape] - - -class GraphRuntimeCodegen(ExprFunctor): +class GraphRuntimeCodegen(object): """The compiler from Relay to the TVM runtime system.""" - nodes = attr.ib() - var_map = attr.ib() def __init__(self, mod, target): - ExprFunctor.__init__(self) - self.mod = mod - self.target = target - self.nodes = [] - self.var_map = {} - self.params = {} - self.storage_device_map = None - self.compile_engine = compile_engine.get() - self.lowered_funcs = defaultdict(set) - self._name_map = {} - - def add_node(self, node, expr): - """ - Add a node to the graph. - - Parameters - ---------- - node: Node - The node to add to the graph. - - expr: tvm.relay.Expr - The corresponding expression. - - Returns - ------- - node_ref: Union[NodeRef, List[NodeRef]] - A reference to the node. - """ - checked_type = expr.checked_type - # setup storage ids - assert expr in self.storage_device_map - storage_device_info = self.storage_device_map[expr] - assert len(storage_device_info) == 2 - node.attrs["storage_id"] = [x.value for x in storage_device_info[0]] - device_types = [x.value for x in storage_device_info[1]] - num_unknown_devices = device_types.count(0) - if num_unknown_devices != 0 and num_unknown_devices != len(device_types): - raise RuntimeError("The graph contains not annotated nodes for " - "heterogeneous execution. All nodes must be " - "annotated.") - - # Add the `device_index` attribute when the graph is annotated. - if num_unknown_devices == 0: - node.attrs["device_index"] = device_types - - node_id = len(self.nodes) - self.nodes.append(node) - # Tuple return value, flatten as tuple - if isinstance(checked_type, TupleType): - ret = [] - shape = [] - dtype = [] - for i, typ in enumerate(checked_type.fields): - if not isinstance(typ, TensorType): - raise RuntimeError("type %s not supported" % typ) - ret.append(NodeRef(node_id, i)) - shape.append(shape_to_json(typ.shape)) - dtype.append(typ.dtype) - node.attrs["shape"] = shape - node.attrs["dtype"] = dtype - assert isinstance(node, OpNode) - node.num_outputs = len(checked_type.fields) - return tuple(ret) - # Normal tensor return type - if not isinstance(checked_type, TensorType): - raise RuntimeError("type %s not supported" % checked_type) - node.attrs["shape"] = [shape_to_json(checked_type.shape)] - node.attrs["dtype"] = [checked_type.dtype] - node.num_outputs = 1 - return NodeRef(node_id, 0) - - def visit_tuple(self, vtuple): - fields = [] - for field in vtuple.fields: - ref = self.visit(field) - assert isinstance(ref, NodeRef) - fields.append(ref) - return tuple(fields) - - def visit_tuple_getitem(self, op): - vtuple = self.visit(op.tuple_value) - assert isinstance(vtuple, tuple) - return vtuple[op.index] - - def visit_constant(self, op): - index = len(self.params) - name = "p%d" % index - self.params[name] = op.data - node = InputNode(name, {}) - return self.add_node(node, op) - - def visit_function(self, _): - raise RuntimeError("function not supported") - - def visit_if(self, _): - raise RuntimeError("if not supported") - - def visit_global_var(self, _): - raise RuntimeError() - - def visit_let(self, let): - """ - Visit the let binding, by first traversing its value, - then setting the metadata on the returned NodeRef. - - Finally visit the body, and return the NodeRef corresponding - to it. - - Parameters - ---------- - let: tvm.relay.Expr - The let binding to transform. - - Returns - ------- - ref: NodeRef - The node reference to the body. - """ - assert let.var not in self.var_map - self.var_map[let.var] = self.visit(let.value) - return self.visit(let.body) - - def visit_var(self, rvar): - return self.var_map[rvar] - - def visit_call(self, call): - """Transform a ::tvm.relay.Call into an operator in the TVM graph.""" - if isinstance(call.op, Op): - raise Exception( - "Operators should be transformed away; try applying" + - "the fuse_ops transformation to the expression.") - elif isinstance(call.op, GlobalVar): - func = self.mod[call.op] - elif isinstance(call.op, Function): - func = call.op - else: - raise Exception( - "TVM runtime does not support calls to {0}".format(type(call.op))) - if int(func.attrs.Primitive) != 1: - raise Exception( - "TVM only support calls to primitive functions " + - "(i.e functions composed of fusable operator invocations)") - - assert call in self.storage_device_map - device_types = self.storage_device_map[call][1] - call_dev_type = device_types[0].value - if isinstance(self.target, (str, _target.Target)): - # homogeneous execution. - cached_func = self.compile_engine.lower(func, self.target) - self.target = {0: str(self.target)} - elif isinstance(self.target, dict): - # heterogeneous execution. - if call_dev_type not in self.target: - raise Exception("No target is provided for device " + - "{0}".format(call_dev_type)) - cached_func = self.compile_engine.lower(func, - self.target[call_dev_type]) - else: - raise ValueError("self.target must be the type of str," + - "tvm.target.Target, or dict of int to str") - for loweredf in cached_func.funcs: - self.lowered_funcs[self.target[call_dev_type]].add(loweredf) - - inputs = [] - # flatten tuple in the call. - for arg in call.args: - res = self.visit(arg) - if isinstance(arg.checked_type, TupleType): - assert isinstance(res, tuple) - inputs += res - else: - inputs.append(res) - - inputs = [x.to_json() for x in inputs] - op_name = cached_func.func_name - op_node = OpNode(self._get_unique_name(op_name), {}, - op_name, inputs, {}) - return self.add_node(op_node, call) - - def visit_op(self, _): - raise Exception("can not compile op in non-eta expanded form") - - def visit_ref_create(self, _): - raise RuntimeError("reference not supported") - - def visit_ref_read(self, _): - raise RuntimeError("reference not supported") - - def visit_ref_write(self, _): - raise RuntimeError("reference not supported") - - def visit_constructor(self, _): - raise Exception("ADT constructor case not yet implemented") - - def visit_match(self, _): - raise Exception("match case not yet implemented") - - def _get_json(self): - """ - Convert the sequence of nodes stored by the compiler into the - TVM graph runtime format. - - Returns - ------- - graph_json : str - The generated JSON as a string. - """ - nodes = [] - # First we compute "nodes" field. - for node in self.nodes: - nodes.append(node.to_json()) - - arg_nodes = [] - # Compute "arg_nodes" and "heads" fields. - for i, node in enumerate(self.nodes): - if isinstance(node, InputNode): - arg_nodes.append(i) - - heads = self.heads - heads = heads if isinstance(heads, tuple) else [heads] - heads = [x.to_json() for x in heads] - - # Compute "node_row_ptr" and entry attributes. - num_entry = 0 - shapes = [] - storage_ids = [] - device_types = [] - dltypes = [] - node_row_ptr = [0] - for node in self.nodes: - assert node.num_outputs == len(node.attrs["shape"]) - shapes += node.attrs["shape"] - dltypes += node.attrs["dtype"] - storage_ids += node.attrs["storage_id"] - if "device_index" in node.attrs: - device_types += node.attrs["device_index"] - num_entry += node.num_outputs - node_row_ptr.append(num_entry) - - # Compute "attrs" field. - attrs = {} - attrs["shape"] = ["list_shape", shapes] - attrs["storage_id"] = ["list_int", storage_ids] - if device_types: - attrs["device_index"] = ["list_int", device_types] - attrs["dltype"] = ["list_str", dltypes] - - # Metadata definitions - def nested_defaultdict(): - return defaultdict(nested_defaultdict) - metadata = nested_defaultdict() - for node_id in arg_nodes: - node_name = nodes[node_id]['name'] - if node_name not in self.params: - metadata['signatures']['default']['inputs'][node_name]['id'] = node_id - metadata['signatures']['default']['inputs'][node_name]['dtype'] = dltypes[node_id] - metadata['signatures']['default']['inputs'][node_name]['shape'] = shapes[node_id] - for node_id in heads: - node_name = nodes[node_id[0]]['name'] - metadata['signatures']['default']['outputs'][node_name]['id'] = node_id[0] - metadata['signatures']['default']['outputs'][node_name]['dtype'] = dltypes[node_id[0]] - metadata['signatures']['default']['outputs'][node_name]['shape'] = shapes[node_id[0]] - - # Keep 'metadata' always at end - json_dict = OrderedDict([ - ("nodes", nodes), - ("arg_nodes", arg_nodes), - ("heads", heads), - ("attrs", attrs), - ("node_row_ptr", node_row_ptr), - ("metadata", metadata), - ]) - - return json.dumps(json_dict, indent=2) - - def debug_dump_memory_plan(self, func): - """Debug function to dump memory plan.""" - def _annotate(expr): - if expr in self.storage_device_map: - storage_device_info = self.storage_device_map[expr] - assert len(storage_device_info) == 2 - return str(storage_device_info[0]) - return "" - return func.astext(show_meta_data=False, annotate=_annotate) - - def debug_dump_device_annotation(self, func): - """Debug function to dump device annotation result.""" - def _annotate(expr): - if expr in self.storage_device_map: - storage_device_info = self.storage_device_map[expr] - assert len(storage_device_info) == 2 - return str(storage_device_info[1]) - return "" - return func.astext(show_meta_data=False, annotate=_annotate) - + self._mod = build_module._GraphRuntimeCodegen() + self._init = self._mod["init"] + self._codegen = self._mod["codegen"] + self._get_graph_json = self._mod["get_graph_json"] + self._list_params_name = self._mod["list_params_name"] + self._get_param_by_name = self._mod["get_param_by_name"] + self._get_lowered_funcs = self._mod["get_lowered_funcs"] + self._setup(mod, target) + + def _setup(self, mod, target): + tgts = [] + if isinstance(target, dict): + for kv in target.items(): + tgts.append(kv[0]) + if isinstance(kv[1], (str, _target.Target)): + tgts.append(str(kv[1])) + else: + raise Exception("Unknown target type") + elif isinstance(target, (str, _target.Target)): + tgts.append("0") + tgts.append(str(target)) + self._init(mod, tgts) def codegen(self, func): """Compile a single function into a graph. @@ -425,38 +82,20 @@ def codegen(self, func): ------- graph_json : str The graph json that can be consumed by runtime. - lowered_funcs : List[tvm.LoweredFunc] or Dict[str, List[tvm.LoweredFunc]] The lowered functions. - params : Dict[str, tvm.nd.NDArray] Additional constant parameters. """ - self.storage_device_map = _backend.GraphPlanMemory(func) - # First we convert all the parameters into input nodes. - for param in func.params: - node = InputNode(param.name_hint, {}) - self.var_map[param] = self.add_node(node, param) - - # Then we compile the body into a graph which can depend - # on input variables. - self.heads = self.visit(func.body) - graph_json = self._get_json() - - # Return the lowered functions as a list for homogeneous compilation. - # Otherwise, for heterogeneous compilation, a dictionary containing - # the device id to a list of lowered functions is returned. Both forms - # are acceptable to tvm.build. - if not isinstance(self.target, dict): - lowered_funcs = list(list(self.lowered_funcs.values())[0]) - else: - lowered_funcs = {k: list(v) for k, v in self.lowered_funcs.items()} - return graph_json, lowered_funcs, self.params - - def _get_unique_name(self, name): - if name not in self._name_map: - self._name_map[name] = 1 - return name - index = self._name_map[name] - self._name_map[name] += 1 - return self._get_unique_name(name + str(index)) + self._codegen(func) + graph_json = self._get_graph_json() + lowered_func = self._get_lowered_funcs() + param_names = self._list_params_name() + params = {} + for name in param_names: + key = name.value + arr = self._get_param_by_name(key) + param = empty(arr.shape, dtype=arr.dtype, ctx=arr.ctx) + arr.copyto(param) + params[key] = param + return graph_json, lowered_func, params diff --git a/src/relay/backend/graph_runtime_codegen.cc b/src/relay/backend/graph_runtime_codegen.cc new file mode 100644 index 000000000000..beb13032ce55 --- /dev/null +++ b/src/relay/backend/graph_runtime_codegen.cc @@ -0,0 +1,760 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2018 by Contributors + * \file relay/backend/graph_codegen.cc + * \brief Graph runtime codegen + */ + +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include "utils.h" +#include "compile_engine.h" + +namespace tvm { +namespace relay { +namespace backend { + +class GraphNode; +class GraphInputNode; +class GraphOpNode; + +using IntegerArray = Array; +using ShapeVector = std::vector >; +using GraphAttrs = std::unordered_map; +using GraphNodePtr = std::shared_ptr; +using GraphInputNodePtr = std::shared_ptr; +using GraphOpNodePtr = std::shared_ptr; +using TargetsMap = std::unordered_map; + +/*! \brief Lowered outputs */ +struct LoweredOutput { + std::string graph_json; + Map > lowered_funcs; + std::unordered_map params; +}; + +/*! \brief Node types */ +enum GraphNodeType { + kGraphNop, + kGraphInputNode, + kGraphOpNode, +}; + +class GraphNodeRef { + public: + GraphNodeRef() {} + GraphNodeRef(int ident, int index, int version = 0) + : ident_(ident), index_(index), version_(version) {} + + + inline void Save(dmlc::JSONWriter* writer) const { + writer->BeginArray(); + writer->WriteArrayItem(ident_); + writer->WriteArrayItem(index_); + writer->WriteArrayItem(version_); + writer->EndArray(); + } + + inline void Load(dmlc::JSONReader* reader) { + LOG(FATAL) << "Not implemented."; + } + + protected: + int ident_; + int index_{0}; + int version_{0}; +}; + +/*! \brief Base Node class */ +class GraphNode { + public: + GraphNode() {} + virtual void Save(dmlc::JSONWriter* writer) const {} + virtual void Load(dmlc::JSONReader* reader) {} + virtual GraphNodeType Type() const { return kGraphNop; } + virtual ~GraphNode() {} + + public: + int num_outputs_{1}; + std::string name_; + GraphAttrs attrs_; +}; + +/*! \brief Input Node */ +class GraphInputNode : public GraphNode { + public: + GraphInputNode() {} + GraphInputNode(const std::string& name, const GraphAttrs& attrs) { + name_ = name; + attrs_ = attrs; + } + + GraphNodeType Type() const override { return kGraphInputNode; } + + void Save(dmlc::JSONWriter* writer) const override { + const std::string op_name{"null"}; + writer->BeginObject(); + writer->WriteObjectKeyValue("op", op_name); + writer->WriteObjectKeyValue("name", this->name_); + writer->WriteObjectKeyValue("inputs", std::list()); + writer->EndObject(); + } + static std::shared_ptr make_node_ptr(const std::string& name, + const GraphAttrs& attrs) { + auto ptr = std::make_shared(name, attrs); + return std::dynamic_pointer_cast(ptr); + } +}; + +/*! \brief Op Node */ +class GraphOpNode : public GraphNode { + public: + GraphOpNode() {} + GraphOpNode(const std::string& name, + const GraphAttrs& nd_attrs, + const std::string& op_name, + const std::vector& inputs, + const GraphAttrs& attrs, + size_t num_outputs = 1) { + name_ = name; + attrs_ = nd_attrs; + op_name_ = op_name; + inputs_ = inputs; + op_attrs_ = attrs_; + num_outputs_ = num_outputs; + op_attrs_["func_name"] = op_name_; + op_attrs_["flatten_data"] = std::string("0"); + op_attrs_["num_inputs"] = std::to_string(inputs_.size()); + op_attrs_["num_outputs"] = std::to_string(num_outputs_); + } + + GraphNodeType Type() const override { return kGraphOpNode; } + + void Save(dmlc::JSONWriter* writer) const override { + GraphAttrs attrs = op_attrs_; + attrs["func_name"] = this->op_name_; + attrs["flatten_data"] = std::string("0"); + attrs["num_inputs"] = std::to_string(this->inputs_.size()); + attrs["num_outputs"] = std::to_string(this->num_outputs_); + writer->BeginObject(); + writer->WriteObjectKeyValue("op", op_type_name_); + writer->WriteObjectKeyValue("name", name_); + writer->WriteObjectKeyValue("attrs", attrs); + writer->WriteObjectKeyValue("inputs", this->inputs_); + writer->EndObject(); + } + static std::shared_ptr make_node_ptr(const std::string& name, + const GraphAttrs& nd_attrs, + const std::string& op_name, + const std::vector& inputs, + const GraphAttrs& attrs, + size_t num_outputs = 1) { + auto ptr = std::make_shared(name, nd_attrs, op_name, inputs, attrs, num_outputs); + return std::dynamic_pointer_cast(ptr); + } + + public: + std::string op_name_; + std::vector inputs_; + GraphAttrs op_attrs_; + + private: + const std::string op_type_name_{"tvm_op"}; +}; + +/*! \brief Code generator for graph runtime */ +class GraphRuntimeCodegen + : public ::tvm::relay::ExprFunctor(const Expr&)> { + public: + GraphRuntimeCodegen(runtime::Module* mod, + const std::unordered_map& targets) : mod_(mod) { + compile_engine_ = CompileEngine::Global(); + for (auto &kv : targets) { + targets_[kv.first] = Target::create(kv.second); + } + } + + LoweredOutput Codegen(relay::Function func) { + auto pf = GetPackedFunc("relay.backend.GraphPlanMemory"); + storage_device_map_ = (*pf)(func); + // First we convert all the parameters into input nodes. + for (auto param : func->params) { + auto node_ptr = GraphInputNode::make_node_ptr(param->name_hint(), GraphAttrs()); + var_map_[param.get()] = AddNode(node_ptr, param); + } + heads_ = VisitExpr(func->body); + std::ostringstream os; + dmlc::JSONWriter writer(&os); + GetJSON(&writer); + LoweredOutput ret; + ret.graph_json = os.str(); + ret.params = params_; + for (auto& kv : lowered_funcs_) { + if (ret.lowered_funcs.count(kv.first) == 0) { + ret.lowered_funcs.Set(kv.first, Array()); + } + auto& vec = ret.lowered_funcs[kv.first]; + Array tmp; + for (auto f : kv.second) { + tmp.push_back(f); + } + for (auto f : vec) { + tmp.push_back(f); + } + ret.lowered_funcs.Set(kv.first, tmp); + } + return ret; + } + + protected: + /*! + * \brief Extract shape from expr to vector + * + * \param shape + * \return std::vector + */ + std::vector _ShapeToJSON(tvm::Array shape) { + std::vector ret; + for (IndexExpr dim : shape) { + const int64_t* pval = as_const_int(dim); + ret.push_back(*pval); + } + return ret; + } + + /*! + * \brief Add node to graph + * + * \param node + * \param expr + * \return std::vector<_NodeRef> + */ + std::vector AddNode(GraphNodePtr node, Expr expr) { + auto checked_type = expr->checked_type(); + size_t count = storage_device_map_.count(expr); + CHECK_GT(count, 0) << "Expr is not existing in storage plan"; + auto storage_device_info = storage_device_map_[expr]; + CHECK_EQ(storage_device_info.size(), 2); + // storage + std::vector storage_info; + for (auto& v : storage_device_info[0]) { + storage_info.push_back(v->value); + } + node->attrs_["storage_id"] = std::move(storage_info); + // type + std::vector device_types; + for (auto& v : storage_device_info[1]) { + device_types.push_back(v->value); + } + size_t num_unknown_devices = std::count(device_types.begin(), device_types.end(), 0); + if (num_unknown_devices != 0 && num_unknown_devices != device_types.size()) { + LOG(FATAL) << "The graph contains not annotated nodes for " + << "heterogeneous execution. All nodes must be " + << "annotated."; + } + if (num_unknown_devices == 0) { + node->attrs_["device_index"] = device_types; + } + auto node_id = nodes_.size(); + nodes_.push_back(node); + // Tuple return value, flatten as tuple + if (const auto* tuple_type = checked_type.as()) { + std::vector ret; + ShapeVector shape; + std::vector dtype; + for (size_t i = 0; i < tuple_type->fields.size(); ++i) { + if (const auto* typ = tuple_type->fields[i].as()) { + ret.push_back(GraphNodeRef(node_id, i)); + shape.emplace_back(_ShapeToJSON(typ->shape)); + dtype.emplace_back(DType2String(typ->dtype)); + } else { + LOG(FATAL) << "type " << checked_type->type_key() << " not supported"; + } + } + CHECK_EQ(node->Type(), kGraphOpNode); + auto op_nd = std::dynamic_pointer_cast(node); + op_nd->attrs_["shape"] = shape; + op_nd->attrs_["dtype"] = dtype; + op_nd->num_outputs_ = tuple_type->fields.size(); + return ret; + } + // Normal tensor return type + if (const auto* tensor_type = checked_type.as()) { + ShapeVector shape; + std::vector dtype; + shape.emplace_back(_ShapeToJSON(tensor_type->shape)); + dtype.emplace_back(DType2String(tensor_type->dtype)); + node->attrs_["shape"] = shape; + node->attrs_["dtype"] = dtype; + } else { + LOG(FATAL) << "type " << checked_type->type_key() << " not supported"; + } + return {GraphNodeRef(node_id, 0)}; + } + + /*! \brief Visitors */ + std::unordered_map, NodeHash, NodeEqual> visitor_cache_; + + std::vector VisitExpr(const Expr& expr) override { + if (visitor_cache_.count(expr)) return visitor_cache_.at(expr); + std::vector res; + if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } else if (expr.as()) { + res = VisitExpr_(expr.as()); + } + visitor_cache_[expr] = res; + return res; + } + + std::vector VisitExpr_(const VarNode* op) override { + Expr expr = GetRef(op); + return var_map_[expr.get()]; + } + + std::vector VisitExpr_(const ConstantNode* op) override { + Expr expr = GetRef(op); + size_t index = params_.size(); + std::string name = "p" + std::to_string(index); + params_[name] = op->data; + auto node = GraphInputNode::make_node_ptr(name, GraphAttrs()); + return AddNode(node, expr); + } + + std::vector VisitExpr_(const TupleNode* op) override { + std::vector fields; + for (auto field : op->fields) { + auto ref_vec = VisitExpr(field); + for (auto ref : ref_vec) { + fields.push_back(ref); + } + } + return fields; + } + std::vector VisitExpr_(const CallNode* op) override { + Expr expr = GetRef(op); + Function func; + if (op->op.as()) { + LOG(FATAL) << "Operators should be transformed away; try applying" + << "the fuse_ops transformation to the expression."; + } else if (op->op.as()) { + LOG(FATAL) << "Not implemented"; + } else if (op->op.as()) { + func = GetRef(op->op.as()); + } else { + LOG(FATAL) << "TVM runtime does not support calls to " << op->op->type_key(); + } + if (!func->IsPrimitive()) { + LOG(FATAL) << "TVM only support calls to primitive functions " + << "(i.e functions composed of fusable operator invocations)"; + } + + CHECK_GE(storage_device_map_.count(expr), 0); + auto pf0 = GetPackedFunc("relay.backend._make_CCacheKey"); + auto pf1 = GetPackedFunc("relay.backend._CompileEngineLower"); + auto &device_type = storage_device_map_[expr][1]; + auto call_dev_type = device_type[0]->value; //-> int to string + Target target; + if (targets_.size() == 1) { + // homogeneous execution. + for (auto kv : targets_) { + target = kv.second; + } + } else { + // heterogeneous execution. + const auto call_dev_key = std::to_string(call_dev_type); + const auto call_dev_name = runtime::DeviceName(call_dev_type); + if (targets_.count(call_dev_name) == 0 && targets_.count(call_dev_key) == 0) { + LOG(FATAL) << "No target is provided for device " + << call_dev_name; + } + if (targets_.count(call_dev_key)) { + target = targets_[call_dev_key]; + } else { + target = targets_[call_dev_name]; + } + } + CCacheKey key = (*pf0)(func, target); + CachedFunc lowerd_func = (*pf1)(compile_engine_, key); + if (!lowered_funcs_.count(target->target_name)) { + lowered_funcs_[target->target_name] = {}; + } + for (auto f : lowerd_func->funcs) { + lowered_funcs_[target->target_name].insert(f); + } + + std::vector inputs; + for (auto arg : op->args) { + auto res = VisitExpr(arg); + for (auto nr : res) { + inputs.push_back(nr); + } + } + auto& op_name = lowerd_func->func_name; + auto node = GraphOpNode::make_node_ptr(_GetUniqueName(op_name), + GraphAttrs(), + op_name, + inputs, + GraphAttrs()); + return AddNode(node, expr); + } + + std::vector VisitExpr_(const LetNode* op) override { + CHECK_EQ(var_map_.count(op->var.get()), 0); + var_map_[op->var.get()] = VisitExpr(op->value); + return VisitExpr(op->body); + } + std::vector VisitExpr_(const TupleGetItemNode* op) override { + auto vtuple = VisitExpr(op->tuple); + return {vtuple[op->index]}; + } + std::vector VisitExpr_(const OpNode* op) override { + throw std::runtime_error("can not compile op in non-eta expanded form"); + return {}; + } + std::vector VisitExpr_(const GlobalVarNode* op) override { + throw std::runtime_error(""); + return {}; + } + std::vector VisitExpr_(const IfNode* op) override { + throw std::invalid_argument("if not supported"); + return {}; + } + std::vector VisitExpr_(const FunctionNode* op) override { + throw std::invalid_argument("function not supported"); + return {}; + } + std::vector VisitExpr_(const RefCreateNode* op) override { + throw std::invalid_argument("reference not supported"); + return {}; + } + std::vector VisitExpr_(const RefReadNode* op) override { + throw std::invalid_argument("reference not supported"); + return {}; + } + std::vector VisitExpr_(const RefWriteNode* op) override { + throw std::invalid_argument("reference not supported"); + return {}; + } + std::vector VisitExpr_(const ConstructorNode* op) override { + throw std::invalid_argument("ADT constructor case not yet implemented"); + return {}; + } + std::vector VisitExpr_(const MatchNode* op) override { + throw std::invalid_argument("match case not yet implemented"); + return {}; + } + /*! + * \brief Generate Graph JSON + * + * \param writer json writer + */ + void GetJSON(dmlc::JSONWriter* writer) { + std::vector arg_nodes; + for (size_t i = 0; i < nodes_.size(); ++i) { + auto node = nodes_[i]; + if (node->Type() == kGraphInputNode) { + arg_nodes.push_back(i); + } + } + size_t num_entry = 0; + ShapeVector shapes; + std::vector storage_ids; + std::vector device_types; + std::vector dltypes; + std::vector node_row_ptr{0}; + for (auto node : nodes_) { + const auto& shape_vec = dmlc::get(node->attrs_["shape"]); + const auto& storage_id = dmlc::get>(node->attrs_["storage_id"]); + const auto& dtype_vec = dmlc::get>(node->attrs_["dtype"]); + + CHECK_EQ(node->num_outputs_, shape_vec.size()); + num_entry += node->num_outputs_; + + shapes.insert(shapes.end(), shape_vec.begin(), shape_vec.end()); + dltypes.insert(dltypes.end(), dtype_vec.begin(), dtype_vec.end()); + storage_ids.insert(storage_ids.end(), storage_id.begin(), storage_id.end()); + if (node->attrs_.count("device_index")) { + const auto& dev_types = dmlc::get>(node->attrs_["device_index"]); + device_types.insert(device_types.end(), dev_types.begin(), dev_types.end()); + } + node_row_ptr.push_back(num_entry); + } + writer->BeginObject(); + writer->WriteObjectKeyValue("nodes", nodes_); + writer->WriteObjectKeyValue("arg_nodes", arg_nodes); + writer->WriteObjectKeyValue("heads", heads_); + std::unordered_map> attrs; + attrs["shape"].emplace_back(std::string("list_shape")); + attrs["shape"].emplace_back(shapes); + attrs["storage_id"].emplace_back(std::string("list_int")); + attrs["storage_id"].emplace_back(storage_ids); + if (device_types.size()) { + attrs["device_index"].emplace_back(std::string("list_int")); + attrs["device_index"].emplace_back(device_types); + } + attrs["dltype"].emplace_back(std::string("list_str")); + attrs["dltype"].emplace_back(dltypes); + writer->WriteObjectKeyValue("attrs", attrs); + writer->WriteObjectKeyValue("node_row_ptr", node_row_ptr); + writer->EndObject(); + } + + /*! + * \brief Get unique name for func + * + * \param name + * \return std::string + */ + std::string _GetUniqueName(const std::string& name) { + if (!name_map_.count(name)) { + name_map_[name] = 1; + return name; + } + auto index = name_map_[name]; + name_map_[name] += 1; + return _GetUniqueName(name + std::to_string(index)); + } + + protected: + /*! \brief nodes */ + std::vector nodes_; + /*! \brief output of graph */ + std::vector heads_; + /*! \brief mod */ + runtime::Module* mod_; + /*! \brief variable map */ + std::unordered_map> var_map_; + /*! \brief target device */ + TargetsMap targets_; + /*! \brief params */ + std::unordered_map params_; + /*! \brief plan memory of device result */ + Map> storage_device_map_; + /*! \brief lowered funcs */ + std::unordered_map> + lowered_funcs_; + /*! \brief name map */ + std::unordered_map name_map_; + /*! \brief compile engine */ + CompileEngine compile_engine_; +}; + +class GraphRuntimeCodegenModule : public runtime::ModuleNode { + public: + GraphRuntimeCodegenModule() {} + virtual PackedFunc GetFunction(const std::string& name, + const std::shared_ptr& sptr_to_self) { + if (name == "init") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + CHECK_EQ(args.num_args, 2) << "The expected of arguments are: " + << "runtime::Module mod and Map targets"; + void* mod = args[0]; + auto& sptr = args[1].node_sptr(); + auto* node = static_cast(sptr.get()); + auto& tmp_targets = node->data; + std::unordered_map targets; + for (size_t i = 0; i < tmp_targets.size(); i += 2) { + std::string key; + auto sk = Expr(tmp_targets[i]).as(); + auto ik = Expr(tmp_targets[i]).as(); + if (sk) { + key = sk->value; + } + if (ik) { + key = std::to_string(ik->value); + } + auto v = Expr(tmp_targets[i + 1]).as(); + targets[key] = v->value; + } + codegen_ = std::make_shared( + reinterpret_cast(mod), targets); + }); + } else if (name == "codegen") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + Function func = args[0]; + this->output_ = this->codegen_->Codegen(func); + }); + } else if (name == "get_graph_json") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + *rv = this->output_.graph_json; + }); + } else if (name == "list_params_name") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + Array ret; + for (const auto &kv : this->output_.params) { + HalideIR::Expr name = ir::StringImm::make(kv.first); + ret.push_back(name); + } + *rv = ret; + }); + + } else if (name == "get_param_by_name") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + std::string key = args[0]; + CHECK_GT(this->output_.params.count(key), 0); + *rv = this->output_.params[key]; + }); + } else if (name == "get_lowered_funcs") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + *rv = this->output_.lowered_funcs; + }); + } else { + return PackedFunc([](TVMArgs args, TVMRetValue* rv) {}); + } + } + + const char* type_key() const final { + return "RelayGraphRuntimeCodegenModule"; + } + + private: + std::shared_ptr codegen_; + LoweredOutput output_; +}; + +runtime::Module CreateGraphCodegenMod() { + std::shared_ptr ptr = + std::make_shared(); + return runtime::Module(ptr); +} + +TVM_REGISTER_GLOBAL("relay.build_module._GraphRuntimeCodegen") +.set_body([](TVMArgs args, TVMRetValue* rv) { + *rv = CreateGraphCodegenMod(); +}); + +} // namespace backend +} // namespace relay +} // namespace tvm + +namespace dmlc { +namespace json { +// JSON utils +template +inline bool SameType(const dmlc::any& data) { + return std::type_index(data.type()) == std::type_index(typeid(T)); +} + +template <> +struct Handler> { + inline static void Write(dmlc::JSONWriter* writer, + const std::shared_ptr& data) { + data->Save(writer); + } + inline static void Read(dmlc::JSONReader* reader, + std::shared_ptr* data) { + LOG(FATAL) << "Not implemented."; + } +}; + +template <> +struct Handler> { + inline static void Write(dmlc::JSONWriter* writer, + const std::unordered_map& data) { + writer->BeginObject(); + for (const auto& kv : data) { + auto k = kv.first; + const dmlc::any& v = kv.second; + if (SameType(v)) { + writer->WriteObjectKeyValue(k, dmlc::get(v)); + } else if (SameType(v)) { + writer->WriteObjectKeyValue(k, dmlc::get(v)); + } else if (SameType>(v)) { + writer->WriteObjectKeyValue(k, dmlc::get>(v)); + } else if (SameType>>(v)) { + writer->WriteObjectKeyValue(k, dmlc::get>>(v)); + } else if (SameType>(v)) { + writer->WriteObjectKeyValue(k, dmlc::get>(v)); + } else { + LOG(FATAL) << "Not supported"; + } + } + writer->EndObject(); + } + inline static void Read(dmlc::JSONReader* reader, + std::unordered_map* data) { + LOG(FATAL) << "Not implemented."; + } +}; + +template <> +struct Handler> { + inline static void Write(dmlc::JSONWriter* writer, const std::vector& data) { + writer->BeginArray(); + for (const auto& v : data) { + if (SameType(v)) { + writer->WriteArrayItem(dmlc::get(v)); + } else if (SameType(v)) { + writer->WriteArrayItem(dmlc::get(v)); + } else if (SameType>(v)) { + writer->WriteArrayItem(dmlc::get>(v)); + } else if (SameType>>(v)) { + writer->WriteArrayItem(dmlc::get>>(v)); + } else if (SameType>(v)) { + writer->WriteArrayItem(dmlc::get>(v)); + } else { + LOG(FATAL) << "Not supported"; + } + } + writer->EndArray(); + } + inline static void Read(dmlc::JSONReader* reader, std::vector* data) { + LOG(FATAL) << "Not implemented."; + } +}; +} // namespace json +} // namespace dmlc diff --git a/src/relay/backend/utils.h b/src/relay/backend/utils.h new file mode 100644 index 000000000000..65a7efd4c205 --- /dev/null +++ b/src/relay/backend/utils.h @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2018 by Contributors + * \file relay/backend/utils.h + * \brief Utils function for backend + */ +#ifndef TVM_RELAY_BACKEND_UTILS_H_ +#define TVM_RELAY_BACKEND_UTILS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace tvm { +namespace relay { +namespace backend { +/*! + * \brief Get the Packed Func + * + * \param func_name + * \return const PackedFunc* + */ +inline const PackedFunc* GetPackedFunc(const std::string& func_name) { + return tvm::runtime::Registry::Get(func_name); +} +/*! + * \brief Convert type to string + * + * \param typ + * \return std::string string format of type + */ +inline std::string DType2String(const tvm::Type typ) { + std::ostringstream os; + auto tvm_type = Type2TVMType(typ); + if (tvm_type.code == kDLFloat) { + os << "float"; + } else if (tvm_type.code == kDLInt) { + os << "int"; + } else if (tvm_type.code == kDLUInt) { + os << "uint"; + } else { + LOG(FATAL) << "Unknown type"; + } + os << typ.bits(); + return os.str(); +} + +} // namespace backend +} // namespace relay +} // namespace tvm + +#endif // TVM_RELAY_BACKEND_UTILS_H_ diff --git a/src/relay/pass/pattern_util.h b/src/relay/pass/pattern_util.h index 1e4060fe6c75..87160b2cd130 100644 --- a/src/relay/pass/pattern_util.h +++ b/src/relay/pass/pattern_util.h @@ -32,7 +32,6 @@ #include #include #include -#include #include diff --git a/tests/python/relay/test_pass_annotation.py b/tests/python/relay/test_pass_annotation.py index ebf9ba913cac..c55a9fb2dd85 100644 --- a/tests/python/relay/test_pass_annotation.py +++ b/tests/python/relay/test_pass_annotation.py @@ -325,7 +325,9 @@ def expected(): annotated_func = annotated() expected_func = expected() - expected_index = [1, 1, 1, 2, 2, 1, 1, 2, 2] + ctx = tvm.context(device, 0) + dev_idx = ctx.device_type + expected_index = [1, 1, 1, dev_idx, dev_idx, 1, 1, dev_idx, dev_idx] check_annotated_graph(annotated_func, expected_func) test_runtime(target, device, annotated_func, fallback_device, expected_index) @@ -401,7 +403,9 @@ def expected(): annotated_func = annotated() expected_func = expected() - expected_index = [2, 2, 2, 1, 1] + ctx = tvm.context(device, 0) + dev_idx = ctx.device_type + expected_index = [dev_idx, dev_idx, dev_idx, 1, 1] check_annotated_graph(annotated_func, expected_func) test_runtime(target, device, annotated_func, fallback_device, expected_index) diff --git a/tests/python/unittest/test_module_load.py b/tests/python/unittest/test_module_load.py index ca508f88f1a7..ba5044825308 100644 --- a/tests/python/unittest/test_module_load.py +++ b/tests/python/unittest/test_module_load.py @@ -82,7 +82,7 @@ def save_object(names): fo.write(runtime_py) subprocess.check_call( - "python %s %s %s" % (path_runtime_py, path_dso, dtype), + "python3 %s %s %s" % (path_runtime_py, path_dso, dtype), shell=True) diff --git a/tests/scripts/task_java_unittest.sh b/tests/scripts/task_java_unittest.sh index 6dc25c825c04..63f16fd755f6 100755 --- a/tests/scripts/task_java_unittest.sh +++ b/tests/scripts/task_java_unittest.sh @@ -26,13 +26,13 @@ CURR_DIR=$(cd `dirname $0`; pwd) SCRIPT_DIR=$CURR_DIR/../../jvm/core/src/test/scripts TEMP_DIR=$(mktemp -d) -python $SCRIPT_DIR/test_add_cpu.py $TEMP_DIR -python $SCRIPT_DIR/test_add_gpu.py $TEMP_DIR -python $SCRIPT_DIR/test_graph_runtime.py $TEMP_DIR +python3 $SCRIPT_DIR/test_add_cpu.py $TEMP_DIR +python3 $SCRIPT_DIR/test_add_gpu.py $TEMP_DIR +python3 $SCRIPT_DIR/test_graph_runtime.py $TEMP_DIR # start rpc proxy server PORT=$(( ( RANDOM % 1000 ) + 9000 )) -python $SCRIPT_DIR/test_rpc_proxy_server.py $PORT 30 & +python3 $SCRIPT_DIR/test_rpc_proxy_server.py $PORT 30 & make jvmpkg make jvmpkg JVM_TEST_ARGS="-DskipTests=false \ diff --git a/tests/scripts/task_python_frontend.sh b/tests/scripts/task_python_frontend.sh index ffcd0a104914..1679595e712b 100755 --- a/tests/scripts/task_python_frontend.sh +++ b/tests/scripts/task_python_frontend.sh @@ -24,14 +24,12 @@ export PYTHONPATH=nnvm/python:python:topi/python export OMP_NUM_THREADS=1 # Rebuild cython -make cython make cython3 echo "Running relay TFLite frontend test..." python3 -m nose -v tests/python/frontend/tflite echo "Running nnvm unittest..." -python -m nose -v nnvm/tests/python/unittest python3 -m nose -v nnvm/tests/python/unittest echo "Running nnvm compiler test..." diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh index 54e41d39e6e2..85dd6de64f6d 100755 --- a/tests/scripts/task_python_integration.sh +++ b/tests/scripts/task_python_integration.sh @@ -25,7 +25,6 @@ export LD_LIBRARY_PATH="build:${LD_LIBRARY_PATH:-}" rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc # Test TVM -make cython make cython3 # Test extern package @@ -33,14 +32,12 @@ cd apps/extension rm -rf lib make cd ../.. -python -m nose -v apps/extension/tests -TVM_FFI=cython python -m nose -v tests/python/integration +python3 -m nose -v apps/extension/tests + TVM_FFI=ctypes python3 -m nose -v tests/python/integration -TVM_FFI=cython python -m nose -v tests/python/contrib TVM_FFI=ctypes python3 -m nose -v tests/python/contrib -TVM_FFI=cython python -m nose -v tests/python/relay TVM_FFI=ctypes python3 -m nose -v tests/python/relay # Do not enable OpenGL diff --git a/tests/scripts/task_python_topi.sh b/tests/scripts/task_python_topi.sh index bd89604c52ef..a204f38c6cc6 100755 --- a/tests/scripts/task_python_topi.sh +++ b/tests/scripts/task_python_topi.sh @@ -22,11 +22,9 @@ set -u export PYTHONPATH=python:topi/python # Rebuild cython -make cython make cython3 rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc rm -rf topi/python/topi/*.pyc topi/python/topi/*/*.pyc topi/python/topi/*/*/*.pyc topi/python/topi/*/*/*/*.pyc -python -m nose -v topi/tests/python python3 -m nose -v topi/tests/python diff --git a/tests/scripts/task_python_unittest.sh b/tests/scripts/task_python_unittest.sh index 6f6b2777d1d0..7879c8d64e11 100755 --- a/tests/scripts/task_python_unittest.sh +++ b/tests/scripts/task_python_unittest.sh @@ -23,9 +23,6 @@ export PYTHONPATH=python:topi/python rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc -TVM_FFI=ctypes python -m nose -v tests/python/unittest TVM_FFI=ctypes python3 -m nose -v tests/python/unittest -make cython make cython3 -TVM_FFI=cython python -m nose -v tests/python/unittest TVM_FFI=cython python3 -m nose -v tests/python/unittest diff --git a/tests/scripts/task_python_vta.sh b/tests/scripts/task_python_vta.sh index be0f0c694aab..4345fc2ba39b 100755 --- a/tests/scripts/task_python_vta.sh +++ b/tests/scripts/task_python_vta.sh @@ -25,13 +25,10 @@ rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc python/tvm/*/*/* rm -rf ~/.tvm # Rebuild cython -make cython make cython3 echo "Running unittest..." -python -m nose -v vta/tests/python/unittest python3 -m nose -v vta/tests/python/unittest echo "Running integration test..." -python -m nose -v vta/tests/python/integration python3 -m nose -v vta/tests/python/integration